gotlin/internal/lang/generate_go.go
Pavel Flegr 8f4f858d86 init
2026-03-05 14:55:09 +01:00

2507 lines
54 KiB
Go

package lang
import (
"bytes"
"fmt"
"go/format"
"regexp"
"strconv"
"strings"
)
func GenerateGo(program *Program) ([]byte, error) {
return generateGo(program, "")
}
func GenerateGoMain(program *Program) ([]byte, error) {
return generateGo(program, "main")
}
func generateGo(program *Program, packageOverride string) ([]byte, error) {
var g goGenerator
if err := g.program(program, packageOverride); err != nil {
return nil, err
}
src := g.buf.Bytes()
formatted, err := format.Source(src)
if err != nil {
return src, fmt.Errorf("generated invalid Go: %w", err)
}
return formatted, nil
}
type goGenerator struct {
buf bytes.Buffer
indentLevel int
needsFmt bool
needsRunCatch bool
needsAutoThrow bool
needsTime bool
needsEveryMs bool
functions map[string]FunctionDecl
classes map[string]ClassDecl
workers map[string]WorkerDecl
currentFunc FunctionDecl
currentClass *ClassDecl
currentWorker *WorkerDecl
currentWorkerFieldReceiver string
scopes []map[string]bool
}
func (g *goGenerator) program(program *Program, packageOverride string) error {
g.functions = make(map[string]FunctionDecl, len(program.Functions))
for _, fn := range program.Functions {
g.functions[fn.Name] = fn
}
g.classes = make(map[string]ClassDecl, len(program.Classes))
for _, class := range program.Classes {
g.classes[class.Name] = class
}
g.workers = make(map[string]WorkerDecl, len(program.Workers))
for _, worker := range program.Workers {
g.workers[worker.Name] = worker
}
packageName := goPackageName(program.PackagePath)
if packageOverride != "" {
packageName = packageOverride
}
g.line("package " + packageName)
g.line("")
for _, fn := range program.Functions {
if usesPrintln(fn.Body) {
g.needsFmt = true
break
}
}
if !g.needsFmt {
for _, class := range program.Classes {
for _, method := range class.Methods {
if usesPrintln(method.Body) {
g.needsFmt = true
break
}
}
if g.needsFmt {
break
}
}
}
if !g.needsFmt {
for _, worker := range program.Workers {
for _, method := range worker.Methods {
if usesPrintln(method.Body) {
g.needsFmt = true
break
}
}
if g.needsFmt {
break
}
}
}
if !g.needsRunCatch {
for _, fn := range program.Functions {
if usesRunCatching(fn.Body) {
g.needsRunCatch = true
break
}
}
}
if !g.needsAutoThrow {
for _, fn := range program.Functions {
if usesAutoThrow(fn.Body) {
g.needsAutoThrow = true
break
}
}
}
if !g.needsAutoThrow {
for _, class := range program.Classes {
for _, method := range class.Methods {
if usesAutoThrow(method.Body) {
g.needsAutoThrow = true
break
}
}
if g.needsAutoThrow {
break
}
}
}
if !g.needsAutoThrow {
for _, worker := range program.Workers {
for _, method := range worker.Methods {
if usesAutoThrow(method.Body) {
g.needsAutoThrow = true
break
}
}
if g.needsAutoThrow {
break
}
}
}
if !g.needsRunCatch {
for _, class := range program.Classes {
for _, method := range class.Methods {
if usesRunCatching(method.Body) {
g.needsRunCatch = true
break
}
}
if g.needsRunCatch {
break
}
}
}
if !g.needsRunCatch {
for _, worker := range program.Workers {
for _, method := range worker.Methods {
if usesRunCatching(method.Body) {
g.needsRunCatch = true
break
}
}
if g.needsRunCatch {
break
}
}
}
for _, fn := range program.Functions {
if usesTimerBuiltins(fn.Body) {
g.needsTime = true
if usesEveryBuiltin(fn.Body) {
g.needsEveryMs = true
}
break
}
}
if !g.needsTime {
for _, class := range program.Classes {
for _, method := range class.Methods {
if usesTimerBuiltins(method.Body) {
g.needsTime = true
if usesEveryBuiltin(method.Body) {
g.needsEveryMs = true
}
break
}
if usesEveryBuiltin(method.Body) {
g.needsEveryMs = true
}
}
if g.needsTime {
break
}
}
}
if !g.needsTime {
for _, worker := range program.Workers {
for _, method := range worker.Methods {
if usesTimerBuiltins(method.Body) {
g.needsTime = true
if usesEveryBuiltin(method.Body) {
g.needsEveryMs = true
}
break
}
if usesEveryBuiltin(method.Body) {
g.needsEveryMs = true
}
}
if g.needsTime {
break
}
}
}
if !g.needsEveryMs {
for _, fn := range program.Functions {
if usesEveryBuiltin(fn.Body) {
g.needsEveryMs = true
break
}
}
}
imports := collectImports(program, g.needsFmt, g.needsTime)
if len(imports) == 1 {
g.line("import " + imports[0])
g.line("")
} else if len(imports) > 1 {
g.line("import (")
g.indentLevel++
for _, imp := range imports {
g.line(imp)
}
g.indentLevel--
g.line(")")
g.line("")
}
if g.needsRunCatch {
g.emitRunCatchingSupport()
g.line("")
}
if g.needsAutoThrow {
g.emitAutoThrowSupport()
g.line("")
}
if g.needsEveryMs {
g.emitEveryMsSupport()
g.line("")
}
emitted := false
for _, decl := range program.Interfaces {
if emitted {
g.line("")
}
g.interfaceDecl(decl)
emitted = true
}
for _, class := range program.Classes {
if emitted {
g.line("")
}
if err := g.classDecl(class); err != nil {
return err
}
emitted = true
}
for _, worker := range program.Workers {
if emitted {
g.line("")
}
if err := g.workerDecl(worker); err != nil {
return err
}
emitted = true
}
for _, fn := range program.Functions {
if emitted {
g.line("")
}
if err := g.function(fn); err != nil {
return err
}
emitted = true
}
return nil
}
func (g *goGenerator) function(fn FunctionDecl) error {
g.currentClass = nil
g.currentWorker = nil
g.currentFunc = fn
g.scopes = nil
g.pushScope()
for _, param := range fn.Params {
g.define(param.Name)
}
g.write("func ")
g.write(fn.Name)
g.write(renderGoParams(fn.Params))
if ret := mapGoReturnType(fn.ReturnType); ret != "" {
g.write(" ")
g.write(ret)
}
g.write(" {\n")
g.indentLevel++
if err := g.block(fn.Body); err != nil {
return err
}
g.indentLevel--
g.line("}")
g.popScope()
return nil
}
func (g *goGenerator) interfaceDecl(decl InterfaceDecl) {
g.line("type " + decl.Name + " interface {")
g.indentLevel++
for _, method := range decl.Methods {
g.line(method.Name + renderGoParams(method.Params) + renderGoReturnSuffix(method.ReturnType))
}
g.indentLevel--
g.line("}")
}
func (g *goGenerator) classDecl(class ClassDecl) error {
g.line("type " + class.Name + " struct {")
g.indentLevel++
for _, field := range class.Fields {
g.line(field.Name + " " + mapGoType(field.Type))
}
g.indentLevel--
g.line("}")
g.line("")
g.write("func New")
g.write(class.Name)
g.write("(")
for i, field := range class.Fields {
if i > 0 {
g.write(", ")
}
g.write(field.Name)
g.write(" ")
g.write(mapGoType(field.Type))
}
g.write(") *")
g.write(class.Name)
g.write(" {\n")
g.indentLevel++
g.writeIndent()
g.write("return &")
g.write(class.Name)
g.write("{")
for i, field := range class.Fields {
if i > 0 {
g.write(", ")
}
g.write(field.Name)
g.write(": ")
g.write(field.Name)
}
g.write("}\n")
g.indentLevel--
g.line("}")
for _, method := range class.Methods {
g.line("")
if err := g.method(class, method); err != nil {
return err
}
}
return nil
}
func (g *goGenerator) workerDecl(worker WorkerDecl) error {
stateName := worker.Name + "State"
g.line("type " + worker.Name + " struct {")
g.indentLevel++
g.line("inbox chan func(*" + stateName + ")")
g.indentLevel--
g.line("}")
g.line("")
g.line("type " + stateName + " struct {")
g.indentLevel++
for _, field := range worker.Fields {
typ := field.Type
if typ == "" {
switch field.Value.(type) {
case IntExpr:
typ = "Int"
case StringExpr:
typ = "String"
case BoolExpr:
typ = "Boolean"
default:
typ = "any"
}
}
g.line(field.Name + " " + mapGoType(typ))
}
g.indentLevel--
g.line("}")
g.line("")
g.write("func New")
g.write(worker.Name)
g.write("() *")
g.write(worker.Name)
g.write(" {\n")
g.indentLevel++
g.line("self := &" + worker.Name + "{")
g.indentLevel++
g.line("inbox: make(chan func(*" + stateName + ")),")
g.indentLevel--
g.line("}")
g.line("state := &" + stateName + "{}")
for _, field := range worker.Fields {
value, err := g.expr(field.Value, field.Type)
if err != nil {
return err
}
value = g.autoThrowValue(field.Value, value)
g.line("state." + field.Name + " = " + value)
}
g.line("go func() {")
g.indentLevel++
g.line("for fn := range self.inbox {")
g.indentLevel++
g.line("fn(state)")
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}()")
g.line("return self")
g.indentLevel--
g.line("}")
for _, method := range worker.Methods {
g.line("")
if err := g.workerMethod(worker, method); err != nil {
return err
}
}
return nil
}
func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
g.currentClass = &class
g.currentFunc = fn
g.scopes = nil
g.pushScope()
g.define("self")
g.define("this")
for _, param := range fn.Params {
g.define(param.Name)
}
g.write("func (self *")
g.write(class.Name)
g.write(") ")
g.write(fn.Name)
g.write(renderGoParams(fn.Params))
if ret := mapGoReturnType(fn.ReturnType); ret != "" {
g.write(" ")
g.write(ret)
}
g.write(" {\n")
g.indentLevel++
if err := g.block(fn.Body); err != nil {
return err
}
g.indentLevel--
g.line("}")
g.popScope()
g.currentClass = nil
return nil
}
func (g *goGenerator) workerMethod(worker WorkerDecl, fn FunctionDecl) error {
g.currentClass = nil
g.currentWorker = &worker
g.currentWorkerFieldReceiver = "state"
g.currentFunc = fn
g.scopes = nil
g.pushScope()
g.define("self")
g.define("this")
for _, param := range fn.Params {
g.define(param.Name)
}
g.write("func (self *")
g.write(worker.Name)
g.write(") ")
g.write(fn.Name)
g.write(renderGoParams(fn.Params))
if ret := mapGoReturnType(fn.ReturnType); ret != "" {
g.write(" ")
g.write(ret)
}
g.write(" {\n")
g.indentLevel++
stateName := worker.Name + "State"
retType := mapGoReturnType(fn.ReturnType)
if retType == "" {
g.line("done := make(chan struct{}, 1)")
g.line("panicCh := make(chan any, 1)")
g.line("self.inbox <- func(state *" + stateName + ") {")
g.indentLevel++
g.line("defer func() {")
g.indentLevel++
g.line("if recovered := recover(); recovered != nil {")
g.indentLevel++
g.line("panicCh <- recovered")
g.indentLevel--
g.line("}")
g.line("done <- struct{}{}")
g.indentLevel--
g.line("}()")
if err := g.block(fn.Body); err != nil {
return err
}
g.indentLevel--
g.line("}")
g.line("<-done")
g.line("select {")
g.indentLevel++
g.line("case recovered := <-panicCh:")
g.indentLevel++
g.line("panic(recovered)")
g.indentLevel--
g.line("default:")
g.indentLevel--
g.line("}")
} else {
g.line("type workerResult struct {")
g.indentLevel++
g.line("value " + retType)
g.line("recovered any")
g.indentLevel--
g.line("}")
g.line("reply := make(chan workerResult, 1)")
g.line("self.inbox <- func(state *" + stateName + ") {")
g.indentLevel++
g.line("result := workerResult{}")
g.line("defer func() {")
g.indentLevel++
g.line("if recovered := recover(); recovered != nil {")
g.indentLevel++
g.line("result.recovered = recovered")
g.indentLevel--
g.line("}")
g.line("reply <- result")
g.indentLevel--
g.line("}()")
g.writeIndent()
g.write("result.value = func()")
g.write(renderGoReturnSuffix(fn.ReturnType))
g.write(" {\n")
g.indentLevel++
if err := g.block(fn.Body); err != nil {
return err
}
g.indentLevel--
g.line("}()")
g.indentLevel--
g.line("}")
g.line("result := <-reply")
g.line("if result.recovered != nil {")
g.indentLevel++
g.line("panic(result.recovered)")
g.indentLevel--
g.line("}")
g.line("return result.value")
}
g.indentLevel--
g.line("}")
g.popScope()
g.currentWorker = nil
g.currentWorkerFieldReceiver = ""
return nil
}
func (g *goGenerator) block(stmts []Stmt) error {
for i, stmt := range stmts {
if err := g.stmt(stmt, stmts[i+1:]); err != nil {
return err
}
}
return nil
}
func (g *goGenerator) stmt(stmt Stmt, tail []Stmt) error {
switch s := stmt.(type) {
case VarDecl:
value, err := g.expr(s.Value, s.Type)
if err != nil {
return err
}
value = g.autoThrowValue(s.Value, value)
if !nameUsedInStmts(s.Name, tail) {
g.line(fmt.Sprintf("_ = %s", value))
return nil
}
if s.Type == "" {
g.line(fmt.Sprintf("%s := %s", s.Name, value))
} else {
g.line(fmt.Sprintf("var %s %s = %s", s.Name, mapGoType(s.Type), value))
}
g.define(s.Name)
case MultiVarDecl:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
assigned := make([]string, len(s.Names))
usedCount := 0
for i, name := range s.Names {
if nameUsedInStmts(name, tail) {
assigned[i] = name
usedCount++
} else {
assigned[i] = "_"
}
}
if usedCount == 0 {
g.line(fmt.Sprintf("%s = %s", strings.Join(assigned, ", "), value))
return nil
}
g.line(fmt.Sprintf("%s := %s", strings.Join(assigned, ", "), value))
for i, name := range s.Names {
if assigned[i] != "_" {
g.define(name)
}
}
case AssignStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
value = g.autoThrowValue(s.Value, value)
g.line(fmt.Sprintf("%s = %s", g.assignTarget(s.Name), value))
case AddAssignStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
value = g.autoThrowValue(s.Value, value)
g.line(fmt.Sprintf("%s += %s", g.assignTarget(s.Name), value))
case MultiAssignStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
names := make([]string, 0, len(s.Names))
for _, name := range s.Names {
names = append(names, g.assignTarget(name))
}
g.line(fmt.Sprintf("%s = %s", strings.Join(names, ", "), value))
case ReturnStmt:
if s.Value == nil {
g.line("return")
} else {
value, err := g.expr(s.Value, g.currentFunc.ReturnType)
if err != nil {
return err
}
value = g.autoThrowValue(s.Value, value)
g.line(fmt.Sprintf("return %s", value))
}
case ThrowStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
g.line(fmt.Sprintf("panic(%s)", value))
case GoStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
g.line("go func() {")
g.indentLevel++
g.line("defer func() {")
g.indentLevel++
g.line("if recovered := recover(); recovered != nil {")
g.indentLevel++
g.line(`println("async panic:", recovered)`)
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}()")
g.line(value)
g.indentLevel--
g.line("}()")
case ExprStmt:
value, err := g.expr(s.Value, "")
if err != nil {
return err
}
g.line(value)
case IfStmt:
cond, err := g.expr(s.Cond, "")
if err != nil {
return err
}
g.writeIndent()
g.write(fmt.Sprintf("if %s {\n", cond))
g.indentLevel++
g.pushScope()
if err := g.block(s.Then); err != nil {
return err
}
g.popScope()
g.indentLevel--
g.writeIndent()
g.write("}")
if len(s.Else) > 0 {
g.write(" else {\n")
g.indentLevel++
g.pushScope()
if err := g.block(s.Else); err != nil {
return err
}
g.popScope()
g.indentLevel--
g.writeIndent()
g.write("}")
}
g.write("\n")
case WhileStmt:
cond, err := g.expr(s.Cond, "")
if err != nil {
return err
}
g.writeIndent()
g.write(fmt.Sprintf("for %s {\n", cond))
g.indentLevel++
g.pushScope()
if err := g.block(s.Body); err != nil {
return err
}
g.popScope()
g.indentLevel--
g.line("}")
case SelectStmt:
g.line("select {")
g.indentLevel++
for _, c := range s.Cases {
source, err := g.selectReceiveChannelExpr(c.Source)
if err != nil {
return err
}
caseUsesIt := nameUsedInStmts("it", c.Body)
g.writeIndent()
if caseUsesIt {
g.write(fmt.Sprintf("case it := <-%s:\n", source))
} else {
g.write(fmt.Sprintf("case <-%s:\n", source))
}
g.indentLevel++
g.pushScope()
if caseUsesIt {
g.define("it")
}
if err := g.block(c.Body); err != nil {
return err
}
g.popScope()
g.indentLevel--
}
g.indentLevel--
g.line("}")
case TryCatchStmt:
g.writeIndent()
g.write("func() {\n")
g.indentLevel++
g.writeIndent()
g.write("defer func() {\n")
g.indentLevel++
g.writeIndent()
g.write("if recovered := recover(); recovered != nil {\n")
g.indentLevel++
g.pushScope()
g.define(s.CatchName)
g.line(fmt.Sprintf("%s := recovered", s.CatchName))
if err := g.block(s.CatchBody); err != nil {
return err
}
g.popScope()
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}()")
g.pushScope()
if err := g.block(s.TryBody); err != nil {
return err
}
g.popScope()
g.indentLevel--
g.line("}()")
default:
return fmt.Errorf("unsupported statement %T", stmt)
}
return nil
}
func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
switch e := expr.(type) {
case IdentExpr:
if g.currentClass != nil {
if e.Name == "this" {
return "self", nil
}
if g.classField(*g.currentClass, e.Name) && !g.isDefined(e.Name) {
return "self." + e.Name, nil
}
}
if g.currentWorker != nil {
if e.Name == "this" {
return "self", nil
}
if g.workerField(*g.currentWorker, e.Name) && !g.isDefined(e.Name) {
receiver := g.currentWorkerFieldReceiver
if receiver == "" {
receiver = "self"
}
return receiver + "." + e.Name, nil
}
}
return e.Name, nil
case IntExpr:
return e.Value, nil
case StringExpr:
return e.Value, nil
case BoolExpr:
if e.Value {
return "true", nil
}
return "false", nil
case NullExpr:
return "nil", nil
case UnaryExpr:
value, err := g.wrapExpr(e.Value, "")
if err != nil {
return "", err
}
return e.Op + value, nil
case BinaryExpr:
left, err := g.wrapExpr(e.Left, "")
if err != nil {
return "", err
}
right, err := g.wrapExpr(e.Right, "")
if err != nil {
return "", err
}
return fmt.Sprintf("%s %s %s", left, e.Op, right), nil
case CallExpr:
if g.currentWorker != nil && g.isWorkerSelfCall(e.Callee) {
return "", fmt.Errorf("worker self-calls are forbidden")
}
if ident, ok := e.Callee.(IdentExpr); ok {
switch ident.Name {
case "listOf", "mutableListOf":
return g.listLiteral(e.Args, e.TypeArgs, expectedType)
case "mapOf", "mutableMapOf":
return g.mapLiteral(e.Args, e.TypeArgs, expectedType)
case "Channel":
return g.channelMakeExpr(e.Args, e.TypeArgs, expectedType)
case "after", "every":
if lowered, handled, err := g.lowerTimerSourceCall(e); handled || err != nil {
return lowered, err
}
}
}
if selector, ok := e.Callee.(SelectorExpr); ok {
switch selector.Name {
case "read":
if len(e.Args) != 0 {
return "", fmt.Errorf("read() expects no arguments")
}
receiver, err := g.expr(selector.Receiver, "")
if err != nil {
return "", err
}
return "<-" + receiver, nil
case "send":
if len(e.Args) != 1 {
return "", fmt.Errorf("send(value) expects exactly one argument")
}
receiver, err := g.expr(selector.Receiver, "")
if err != nil {
return "", err
}
arg, err := g.expr(e.Args[0], "")
if err != nil {
return "", err
}
return receiver + " <- " + arg, nil
}
}
args := make([]string, 0, len(e.Args))
argTypes := g.callArgTypes(e.Callee, len(e.Args))
for i, arg := range e.Args {
argType := ""
if i < len(argTypes) {
argType = argTypes[i]
}
value, err := g.expr(arg, argType)
if err != nil {
return "", err
}
args = append(args, value)
}
if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "println" {
return fmt.Sprintf("fmt.Println(%s)", strings.Join(args, ", ")), nil
}
if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "runCatching" {
if len(args) != 1 {
return "", fmt.Errorf("runCatching expects exactly one argument")
}
return fmt.Sprintf("gotlinRunCatching(%s)", args[0]), nil
}
if ident, ok := e.Callee.(IdentExpr); ok {
if _, ok := g.classes[ident.Name]; ok {
return fmt.Sprintf("New%s(%s)", ident.Name, strings.Join(args, ", ")), nil
}
if _, ok := g.workers[ident.Name]; ok {
if len(args) != 0 {
return "", fmt.Errorf("worker constructor %s does not accept arguments", ident.Name)
}
return fmt.Sprintf("New%s()", ident.Name), nil
}
}
callee, err := g.expr(e.Callee, "")
if err != nil {
return "", err
}
return fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", ")), nil
case SelectorExpr:
receiver, err := g.expr(e.Receiver, "")
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%s", receiver, e.Name), nil
case LambdaExpr:
return g.lambda(e, expectedType)
default:
return "", fmt.Errorf("unsupported expression %T", expr)
}
}
func (g *goGenerator) listLiteral(args []Expr, typeArgs []string, expectedType string) (string, error) {
elemType := ""
if len(typeArgs) > 0 {
if len(typeArgs) != 1 {
return "", fmt.Errorf("listOf expects exactly one type argument")
}
elemType = typeArgs[0]
}
if base, genericArgs, ok := parseGenericType(expectedType); ok && (base == "List" || base == "MutableList") && len(genericArgs) == 1 {
if elemType == "" {
elemType = genericArgs[0]
}
}
goElemType := "any"
if elemType != "" {
goElemType = mapGoType(elemType)
}
values := make([]string, 0, len(args))
for _, arg := range args {
value, err := g.expr(arg, elemType)
if err != nil {
return "", err
}
values = append(values, value)
}
return "[]" + goElemType + "{" + strings.Join(values, ", ") + "}", nil
}
func (g *goGenerator) mapLiteral(args []Expr, typeArgs []string, expectedType string) (string, error) {
if len(args)%2 != 0 {
return "", fmt.Errorf("mapOf expects an even number of arguments (key/value pairs)")
}
keyType := ""
valType := ""
if len(typeArgs) > 0 {
if len(typeArgs) != 2 {
return "", fmt.Errorf("mapOf expects exactly two type arguments")
}
keyType = typeArgs[0]
valType = typeArgs[1]
}
if base, genericArgs, ok := parseGenericType(expectedType); ok && (base == "Map" || base == "MutableMap") && len(genericArgs) == 2 {
if keyType == "" {
keyType = genericArgs[0]
}
if valType == "" {
valType = genericArgs[1]
}
}
goKeyType := "any"
goValType := "any"
if keyType != "" {
goKeyType = mapGoType(keyType)
}
if valType != "" {
goValType = mapGoType(valType)
}
var entries []string
for i := 0; i < len(args); i += 2 {
key, err := g.expr(args[i], keyType)
if err != nil {
return "", err
}
val, err := g.expr(args[i+1], valType)
if err != nil {
return "", err
}
entries = append(entries, key+": "+val)
}
return "map[" + goKeyType + "]" + goValType + "{" + strings.Join(entries, ", ") + "}", nil
}
func (g *goGenerator) channelMakeExpr(args []Expr, typeArgs []string, expectedType string) (string, error) {
elemType := ""
if len(typeArgs) > 0 {
if len(typeArgs) != 1 {
return "", fmt.Errorf("Channel expects exactly one type argument")
}
elemType = typeArgs[0]
}
if elemType == "" {
if base, genericArgs, ok := parseGenericType(expectedType); ok && base == "Channel" && len(genericArgs) == 1 {
elemType = genericArgs[0]
}
}
goElemType := "any"
if elemType != "" {
goElemType = mapGoType(elemType)
}
if len(args) > 1 {
return "", fmt.Errorf("Channel() expects at most one capacity argument")
}
if len(args) == 1 {
capacity, err := g.expr(args[0], "Int")
if err != nil {
return "", err
}
return "make(chan " + goElemType + ", " + capacity + ")", nil
}
return "make(chan " + goElemType + ")", nil
}
func (g *goGenerator) lowerTimerSourceCall(call CallExpr) (string, bool, error) {
ident, ok := call.Callee.(IdentExpr)
if !ok {
return "", false, nil
}
if ident.Name != "after" && ident.Name != "every" {
return "", false, nil
}
if len(call.TypeArgs) > 0 {
return "", true, fmt.Errorf("%s does not accept type arguments", ident.Name)
}
if len(call.Args) != 1 {
return "", true, fmt.Errorf("%s(ms) expects exactly one Int argument", ident.Name)
}
msExpr := call.Args[0]
switch msExpr.(type) {
case StringExpr, BoolExpr, NullExpr:
return "", true, fmt.Errorf("%s(ms) expects an Int argument", ident.Name)
}
msValue, err := g.expr(msExpr, "Int")
if err != nil {
return "", true, err
}
if ident.Name == "every" {
if parsed, ok := staticIntValue(msExpr); ok && parsed <= 0 {
return "", true, fmt.Errorf("every(ms) requires ms > 0")
}
}
if ident.Name == "after" {
return "time.After(time.Duration(" + msValue + ") * time.Millisecond)", true, nil
}
g.needsEveryMs = true
return "gotlinEveryMs(" + msValue + ")", true, nil
}
func (g *goGenerator) selectReceiveChannelExpr(source Expr) (string, error) {
if call, ok := source.(CallExpr); ok {
if lowered, handled, err := g.lowerTimerSourceCall(call); handled || err != nil {
return lowered, err
}
}
return g.expr(source, "")
}
func (g *goGenerator) wrapExpr(expr Expr, expectedType string) (string, error) {
switch expr.(type) {
case BinaryExpr:
value, err := g.expr(expr, expectedType)
if err != nil {
return "", err
}
return "(" + value + ")", nil
default:
return g.expr(expr, expectedType)
}
}
func (g *goGenerator) line(text string) {
g.writeIndent()
g.write(text)
g.write("\n")
}
func (g *goGenerator) writeIndent() {
g.write(strings.Repeat("\t", g.indentLevel))
}
func (g *goGenerator) write(text string) {
g.buf.WriteString(text)
}
func mapGoType(name string) string {
if params, ret, ok := parseFunctionType(name); ok {
var goParams []string
for _, param := range params {
goParams = append(goParams, mapGoType(param))
}
goRet := mapGoType(ret)
if goRet == "" {
return "func(" + strings.Join(goParams, ", ") + ")"
}
return "func(" + strings.Join(goParams, ", ") + ") " + goRet
}
if base, args, ok := parseGenericType(name); ok {
mapped := make([]string, 0, len(args))
for _, arg := range args {
mapped = append(mapped, mapGoType(arg))
}
switch base {
case "List", "MutableList":
if len(mapped) == 1 {
return "[]" + mapped[0]
}
case "Map", "MutableMap":
if len(mapped) == 2 {
return "map[" + mapped[0] + "]" + mapped[1]
}
case "Channel":
if len(mapped) == 1 {
return "chan " + mapped[0]
}
}
return base + "[" + strings.Join(mapped, ", ") + "]"
}
switch name {
case "Int":
return "int"
case "String":
return "string"
case "Boolean":
return "bool"
case "Unit":
return ""
default:
return name
}
}
func mapGoReturnType(name string) string {
return mapGoType(name)
}
func usesPrintln(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case ExprStmt:
if call, ok := s.Value.(CallExpr); ok && isBuiltinPrintln(call.Callee) {
return true
}
if exprUsesPrintln(s.Value) {
return true
}
case VarDecl:
if exprUsesPrintln(s.Value) {
return true
}
case AssignStmt:
if exprUsesPrintln(s.Value) {
return true
}
case AddAssignStmt:
if exprUsesPrintln(s.Value) {
return true
}
case MultiAssignStmt:
if exprUsesPrintln(s.Value) {
return true
}
case MultiVarDecl:
if exprUsesPrintln(s.Value) {
return true
}
case ReturnStmt:
if s.Value != nil && exprUsesPrintln(s.Value) {
return true
}
case ThrowStmt:
if exprUsesPrintln(s.Value) {
return true
}
case GoStmt:
if exprUsesPrintln(s.Value) {
return true
}
case IfStmt:
if exprUsesPrintln(s.Cond) || usesPrintln(s.Then) || usesPrintln(s.Else) {
return true
}
case WhileStmt:
if exprUsesPrintln(s.Cond) || usesPrintln(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if exprUsesPrintln(c.Source) || usesPrintln(c.Body) {
return true
}
}
case TryCatchStmt:
if usesPrintln(s.TryBody) || usesPrintln(s.CatchBody) {
return true
}
}
}
return false
}
func usesRunCatching(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case VarDecl:
if exprUsesRunCatching(s.Value) {
return true
}
case MultiVarDecl:
if exprUsesRunCatching(s.Value) {
return true
}
case AssignStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case AddAssignStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case MultiAssignStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case ReturnStmt:
if s.Value != nil && exprUsesRunCatching(s.Value) {
return true
}
case ThrowStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case GoStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case ExprStmt:
if exprUsesRunCatching(s.Value) {
return true
}
case IfStmt:
if exprUsesRunCatching(s.Cond) || usesRunCatching(s.Then) || usesRunCatching(s.Else) {
return true
}
case WhileStmt:
if exprUsesRunCatching(s.Cond) || usesRunCatching(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if exprUsesRunCatching(c.Source) || usesRunCatching(c.Body) {
return true
}
}
case TryCatchStmt:
if usesRunCatching(s.TryBody) || usesRunCatching(s.CatchBody) {
return true
}
}
}
return false
}
func usesAutoThrow(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case VarDecl:
if _, ok := s.Value.(CallExpr); ok {
return true
}
case AssignStmt:
if _, ok := s.Value.(CallExpr); ok {
return true
}
case AddAssignStmt:
if _, ok := s.Value.(CallExpr); ok {
return true
}
case ReturnStmt:
if s.Value != nil {
if _, ok := s.Value.(CallExpr); ok {
return true
}
}
case IfStmt:
if usesAutoThrow(s.Then) || usesAutoThrow(s.Else) {
return true
}
case WhileStmt:
if usesAutoThrow(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if usesAutoThrow(c.Body) {
return true
}
}
case TryCatchStmt:
if usesAutoThrow(s.TryBody) || usesAutoThrow(s.CatchBody) {
return true
}
}
}
return false
}
func usesTimerBuiltins(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case VarDecl:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case MultiVarDecl:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case AssignStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case AddAssignStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case MultiAssignStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case ReturnStmt:
if s.Value != nil && exprUsesTimerBuiltins(s.Value) {
return true
}
case ThrowStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case GoStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case ExprStmt:
if exprUsesTimerBuiltins(s.Value) {
return true
}
case IfStmt:
if exprUsesTimerBuiltins(s.Cond) || usesTimerBuiltins(s.Then) || usesTimerBuiltins(s.Else) {
return true
}
case WhileStmt:
if exprUsesTimerBuiltins(s.Cond) || usesTimerBuiltins(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if exprUsesTimerBuiltins(c.Source) || usesTimerBuiltins(c.Body) {
return true
}
}
case TryCatchStmt:
if usesTimerBuiltins(s.TryBody) || usesTimerBuiltins(s.CatchBody) {
return true
}
}
}
return false
}
func usesEveryBuiltin(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case VarDecl:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case MultiVarDecl:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case AssignStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case AddAssignStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case MultiAssignStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case ReturnStmt:
if s.Value != nil && exprUsesEveryBuiltin(s.Value) {
return true
}
case ThrowStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case GoStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case ExprStmt:
if exprUsesEveryBuiltin(s.Value) {
return true
}
case IfStmt:
if exprUsesEveryBuiltin(s.Cond) || usesEveryBuiltin(s.Then) || usesEveryBuiltin(s.Else) {
return true
}
case WhileStmt:
if exprUsesEveryBuiltin(s.Cond) || usesEveryBuiltin(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if exprUsesEveryBuiltin(c.Source) || usesEveryBuiltin(c.Body) {
return true
}
}
case TryCatchStmt:
if usesEveryBuiltin(s.TryBody) || usesEveryBuiltin(s.CatchBody) {
return true
}
}
}
return false
}
func (g *goGenerator) lambda(lambda LambdaExpr, expectedType string) (string, error) {
params := lambda.Params
returnType := ""
paramTypes := []string(nil)
if lambda.ImplicitIt {
expectedReturnType := ""
ok := false
paramTypes, expectedReturnType, ok = parseFunctionType(expectedType)
if !ok {
return "", fmt.Errorf("lambda with implicit it requires a function type context")
}
if len(paramTypes) == 1 {
params = []Param{{Name: "it", Type: paramTypes[0]}}
} else if len(paramTypes) == 0 {
params = nil
} else {
params = make([]Param, 0, len(paramTypes))
for _, typ := range paramTypes {
params = append(params, Param{Name: "_", Type: typ})
}
}
returnType = expectedReturnType
} else if parsedParamTypes, expectedReturnType, ok := parseFunctionType(expectedType); ok && len(parsedParamTypes) == len(params) {
paramTypes = parsedParamTypes
returnType = expectedReturnType
}
if returnType == "" && lambdaHasValueReturn(lambda.Body) {
return "", fmt.Errorf("lambda with value return requires a function type context")
}
for i := range params {
if params[i].Type == "" {
if i >= len(paramTypes) || paramTypes[i] == "" {
return "", fmt.Errorf("lambda parameter %q requires a type or typed call context", params[i].Name)
}
params[i].Type = paramTypes[i]
}
}
var b strings.Builder
b.WriteString("func(")
for i, param := range params {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(param.Name)
b.WriteString(" ")
b.WriteString(mapGoType(param.Type))
}
b.WriteString(")")
if goRet := mapGoType(returnType); goRet != "" {
b.WriteString(" ")
b.WriteString(goRet)
}
b.WriteString(" {\n")
sub := goGenerator{
indentLevel: 1,
needsFmt: g.needsFmt,
functions: g.functions,
classes: g.classes,
currentFunc: FunctionDecl{ReturnType: returnType},
currentClass: g.currentClass,
}
if g.currentClass != nil {
classCopy := *g.currentClass
sub.currentClass = &classCopy
}
sub.scopes = g.cloneScopes()
sub.pushScope()
if lambda.ImplicitIt {
sub.define("it")
}
for _, param := range params {
sub.define(param.Name)
}
if err := sub.block(lambda.Body); err != nil {
return "", err
}
b.Write(sub.buf.Bytes())
b.WriteString("}")
return b.String(), nil
}
func (g *goGenerator) callArgTypes(callee Expr, argCount int) []string {
ident, ok := callee.(IdentExpr)
if ok {
if ident.Name == "runCatching" {
return []string{"() -> Unit"}
}
fn, ok := g.functions[ident.Name]
if ok {
argTypes := make([]string, 0, min(argCount, len(fn.Params)))
for i := 0; i < argCount && i < len(fn.Params); i++ {
argTypes = append(argTypes, fn.Params[i].Type)
}
return argTypes
}
class, ok := g.classes[ident.Name]
if ok {
argTypes := make([]string, 0, min(argCount, len(class.Fields)))
for i := 0; i < argCount && i < len(class.Fields); i++ {
argTypes = append(argTypes, class.Fields[i].Type)
}
return argTypes
}
return nil
}
selector, ok := selectorPath(callee)
if !ok {
return nil
}
switch selector {
case "http.HandleFunc":
return []string{"String", "(http.ResponseWriter, *http.Request) -> Unit"}
default:
return nil
}
}
func nameUsedInStmts(name string, stmts []Stmt) bool {
return nameUsedInStmtsWithShadow(name, stmts, false)
}
func nameUsedInStmtsWithShadow(name string, stmts []Stmt, shadowed bool) bool {
localShadowed := shadowed
for _, stmt := range stmts {
switch s := stmt.(type) {
case VarDecl:
if exprUsesName(s.Value, name, localShadowed) {
return true
}
if s.Name == name {
localShadowed = true
}
case MultiVarDecl:
if exprUsesName(s.Value, name, localShadowed) {
return true
}
for _, declared := range s.Names {
if declared == name {
localShadowed = true
break
}
}
case AssignStmt:
if !localShadowed && s.Name == name {
return true
}
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case AddAssignStmt:
if !localShadowed && s.Name == name {
return true
}
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case MultiAssignStmt:
if !localShadowed {
for _, assigned := range s.Names {
if assigned == name {
return true
}
}
}
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case ReturnStmt:
if s.Value != nil && exprUsesName(s.Value, name, localShadowed) {
return true
}
case ThrowStmt:
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case GoStmt:
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case ExprStmt:
if exprUsesName(s.Value, name, localShadowed) {
return true
}
case IfStmt:
if exprUsesName(s.Cond, name, localShadowed) {
return true
}
if nameUsedInStmtsWithShadow(name, s.Then, localShadowed) {
return true
}
if nameUsedInStmtsWithShadow(name, s.Else, localShadowed) {
return true
}
case WhileStmt:
if exprUsesName(s.Cond, name, localShadowed) {
return true
}
if nameUsedInStmtsWithShadow(name, s.Body, localShadowed) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if exprUsesName(c.Source, name, localShadowed) {
return true
}
if nameUsedInStmtsWithShadow(name, c.Body, localShadowed) {
return true
}
}
case TryCatchStmt:
if nameUsedInStmtsWithShadow(name, s.TryBody, localShadowed) {
return true
}
catchShadowed := localShadowed || s.CatchName == name
if nameUsedInStmtsWithShadow(name, s.CatchBody, catchShadowed) {
return true
}
}
}
return false
}
func exprUsesName(expr Expr, name string, shadowed bool) bool {
switch e := expr.(type) {
case IdentExpr:
return !shadowed && e.Name == name
case UnaryExpr:
return exprUsesName(e.Value, name, shadowed)
case BinaryExpr:
return exprUsesName(e.Left, name, shadowed) || exprUsesName(e.Right, name, shadowed)
case CallExpr:
if exprUsesName(e.Callee, name, shadowed) {
return true
}
for _, arg := range e.Args {
if exprUsesName(arg, name, shadowed) {
return true
}
}
return false
case SelectorExpr:
return exprUsesName(e.Receiver, name, shadowed)
case LambdaExpr:
lambdaShadowed := shadowed
if e.ImplicitIt && name == "it" {
lambdaShadowed = true
}
for _, param := range e.Params {
if param.Name == name {
lambdaShadowed = true
break
}
}
return nameUsedInStmtsWithShadow(name, e.Body, lambdaShadowed)
default:
return false
}
}
func (g *goGenerator) pushScope() {
g.scopes = append(g.scopes, map[string]bool{})
}
func (g *goGenerator) popScope() {
if len(g.scopes) == 0 {
return
}
g.scopes = g.scopes[:len(g.scopes)-1]
}
func (g *goGenerator) define(name string) {
if len(g.scopes) == 0 {
g.pushScope()
}
g.scopes[len(g.scopes)-1][name] = true
}
func (g *goGenerator) isDefined(name string) bool {
for i := len(g.scopes) - 1; i >= 0; i-- {
if g.scopes[i][name] {
return true
}
}
return false
}
func (g *goGenerator) classField(class ClassDecl, name string) bool {
for _, field := range class.Fields {
if field.Name == name {
return true
}
}
return false
}
func (g *goGenerator) workerField(worker WorkerDecl, name string) bool {
for _, field := range worker.Fields {
if field.Name == name {
return true
}
}
return false
}
func (g *goGenerator) workerHasMethod(worker WorkerDecl, name string) bool {
for _, method := range worker.Methods {
if method.Name == name {
return true
}
}
return false
}
func (g *goGenerator) isWorkerSelfCall(callee Expr) bool {
if g.currentWorker == nil {
return false
}
worker := *g.currentWorker
switch c := callee.(type) {
case IdentExpr:
if !g.workerHasMethod(worker, c.Name) {
return false
}
return !g.isDefined(c.Name)
case SelectorExpr:
if !g.workerHasMethod(worker, c.Name) {
return false
}
recv, ok := c.Receiver.(IdentExpr)
if !ok {
return false
}
return recv.Name == "self" || recv.Name == "this"
default:
return false
}
}
func (g *goGenerator) assignTarget(name string) string {
if g.currentClass != nil && g.classField(*g.currentClass, name) && !g.isDefined(name) {
return "self." + name
}
if g.currentWorker != nil && g.workerField(*g.currentWorker, name) && !g.isDefined(name) {
receiver := g.currentWorkerFieldReceiver
if receiver == "" {
receiver = "self"
}
return receiver + "." + name
}
return name
}
func (g *goGenerator) cloneScopes() []map[string]bool {
dup := make([]map[string]bool, 0, len(g.scopes))
for _, scope := range g.scopes {
copyScope := make(map[string]bool, len(scope))
for k, v := range scope {
copyScope[k] = v
}
dup = append(dup, copyScope)
}
return dup
}
func renderGoParams(params []Param) string {
var b strings.Builder
b.WriteString("(")
for i, param := range params {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(param.Name)
b.WriteString(" ")
b.WriteString(mapGoType(param.Type))
}
b.WriteString(")")
return b.String()
}
func renderGoReturnSuffix(returnType string) string {
if ret := mapGoReturnType(returnType); ret != "" {
return " " + ret
}
return ""
}
func exprUsesPrintln(expr Expr) bool {
switch e := expr.(type) {
case CallExpr:
if isBuiltinPrintln(e.Callee) {
return true
}
for _, arg := range e.Args {
if exprUsesPrintln(arg) {
return true
}
}
case UnaryExpr:
return exprUsesPrintln(e.Value)
case BinaryExpr:
return exprUsesPrintln(e.Left) || exprUsesPrintln(e.Right)
case SelectorExpr:
return exprUsesPrintln(e.Receiver)
case LambdaExpr:
return usesPrintln(e.Body)
}
return false
}
func exprUsesRunCatching(expr Expr) bool {
switch e := expr.(type) {
case CallExpr:
if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "runCatching" {
return true
}
if exprUsesRunCatching(e.Callee) {
return true
}
for _, arg := range e.Args {
if exprUsesRunCatching(arg) {
return true
}
}
case UnaryExpr:
return exprUsesRunCatching(e.Value)
case BinaryExpr:
return exprUsesRunCatching(e.Left) || exprUsesRunCatching(e.Right)
case SelectorExpr:
return exprUsesRunCatching(e.Receiver)
case LambdaExpr:
return usesRunCatching(e.Body)
}
return false
}
func exprUsesTimerBuiltins(expr Expr) bool {
switch e := expr.(type) {
case CallExpr:
if ident, ok := e.Callee.(IdentExpr); ok && (ident.Name == "after" || ident.Name == "every") {
return true
}
if exprUsesTimerBuiltins(e.Callee) {
return true
}
for _, arg := range e.Args {
if exprUsesTimerBuiltins(arg) {
return true
}
}
case UnaryExpr:
return exprUsesTimerBuiltins(e.Value)
case BinaryExpr:
return exprUsesTimerBuiltins(e.Left) || exprUsesTimerBuiltins(e.Right)
case SelectorExpr:
return exprUsesTimerBuiltins(e.Receiver)
case LambdaExpr:
return usesTimerBuiltins(e.Body)
}
return false
}
func exprUsesEveryBuiltin(expr Expr) bool {
switch e := expr.(type) {
case CallExpr:
if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "every" {
return true
}
if exprUsesEveryBuiltin(e.Callee) {
return true
}
for _, arg := range e.Args {
if exprUsesEveryBuiltin(arg) {
return true
}
}
case UnaryExpr:
return exprUsesEveryBuiltin(e.Value)
case BinaryExpr:
return exprUsesEveryBuiltin(e.Left) || exprUsesEveryBuiltin(e.Right)
case SelectorExpr:
return exprUsesEveryBuiltin(e.Receiver)
case LambdaExpr:
return usesEveryBuiltin(e.Body)
}
return false
}
func isBuiltinPrintln(expr Expr) bool {
ident, ok := expr.(IdentExpr)
return ok && ident.Name == "println"
}
func collectImports(program *Program, needsFmt bool, needsTime bool) []string {
seen := map[string]bool{}
var imports []string
usedAliases := usedImportAliases(program)
if needsFmt {
seen[`"fmt"`] = true
imports = append(imports, `"fmt"`)
}
if needsTime {
seen[`"time"`] = true
imports = append(imports, `"time"`)
}
for _, imp := range program.Imports {
path := imp.Path
goPath := path
if !strings.HasPrefix(path, `"`) {
goPath = importPathToGoPath(path)
path = `"` + goPath + `"`
}
rendered := path
if imp.Alias != "" {
rendered = imp.Alias + " " + path
} else if !usedAliases[defaultImportAlias(imp)] {
rendered = "_ " + path
}
if seen[rendered] {
continue
}
seen[rendered] = true
imports = append(imports, rendered)
}
return imports
}
func goPackageName(packagePath string) string {
if packagePath == "" {
return "main"
}
parts := strings.Split(packagePath, ".")
return parts[len(parts)-1]
}
func usedImportAliases(program *Program) map[string]bool {
used := map[string]bool{}
markType := func(typ string) {
for _, alias := range typeImportAliases(typ) {
used[alias] = true
}
}
var walkExpr func(expr Expr)
var walkStmt func(stmt Stmt)
walkExpr = func(expr Expr) {
switch e := expr.(type) {
case IdentExpr:
used[e.Name] = true
case UnaryExpr:
walkExpr(e.Value)
case BinaryExpr:
walkExpr(e.Left)
walkExpr(e.Right)
case CallExpr:
walkExpr(e.Callee)
for _, arg := range e.Args {
walkExpr(arg)
}
case SelectorExpr:
if alias, ok := selectorRootAlias(e); ok {
used[alias] = true
}
walkExpr(e.Receiver)
case LambdaExpr:
for _, param := range e.Params {
markType(param.Type)
}
for _, stmt := range e.Body {
walkStmt(stmt)
}
}
}
walkStmt = func(stmt Stmt) {
switch s := stmt.(type) {
case VarDecl:
markType(s.Type)
walkExpr(s.Value)
case MultiVarDecl:
walkExpr(s.Value)
case AssignStmt:
walkExpr(s.Value)
case AddAssignStmt:
walkExpr(s.Value)
case MultiAssignStmt:
walkExpr(s.Value)
case ReturnStmt:
if s.Value != nil {
walkExpr(s.Value)
}
case ThrowStmt:
walkExpr(s.Value)
case GoStmt:
walkExpr(s.Value)
case ExprStmt:
walkExpr(s.Value)
case IfStmt:
walkExpr(s.Cond)
for _, inner := range s.Then {
walkStmt(inner)
}
for _, inner := range s.Else {
walkStmt(inner)
}
case WhileStmt:
walkExpr(s.Cond)
for _, inner := range s.Body {
walkStmt(inner)
}
case SelectStmt:
for _, c := range s.Cases {
walkExpr(c.Source)
for _, inner := range c.Body {
walkStmt(inner)
}
}
case TryCatchStmt:
for _, inner := range s.TryBody {
walkStmt(inner)
}
for _, inner := range s.CatchBody {
walkStmt(inner)
}
}
}
for _, decl := range program.Interfaces {
for _, method := range decl.Methods {
for _, param := range method.Params {
markType(param.Type)
}
markType(method.ReturnType)
}
}
for _, decl := range program.Classes {
for _, field := range decl.Fields {
markType(field.Type)
}
for _, method := range decl.Methods {
for _, param := range method.Params {
markType(param.Type)
}
markType(method.ReturnType)
for _, stmt := range method.Body {
walkStmt(stmt)
}
}
}
for _, decl := range program.Workers {
for _, field := range decl.Fields {
markType(field.Type)
walkExpr(field.Value)
}
for _, method := range decl.Methods {
for _, param := range method.Params {
markType(param.Type)
}
markType(method.ReturnType)
for _, stmt := range method.Body {
walkStmt(stmt)
}
}
}
for _, fn := range program.Functions {
for _, param := range fn.Params {
markType(param.Type)
}
markType(fn.ReturnType)
for _, stmt := range fn.Body {
walkStmt(stmt)
}
}
return used
}
func selectorRootAlias(expr SelectorExpr) (string, bool) {
switch r := expr.Receiver.(type) {
case IdentExpr:
return r.Name, true
case SelectorExpr:
return selectorRootAlias(r)
default:
return "", false
}
}
func typeImportAliases(typ string) []string {
if typ == "" {
return nil
}
matches := regexp.MustCompile(`([A-Za-z_][A-Za-z0-9_]*)\.`).FindAllStringSubmatch(typ, -1)
out := make([]string, 0, len(matches))
for _, m := range matches {
if len(m) == 2 {
out = append(out, m[1])
}
}
return out
}
func defaultImportAlias(imp ImportDecl) string {
if imp.Alias != "" {
return imp.Alias
}
path := imp.Path
if strings.HasPrefix(path, `"`) {
path = strings.Trim(path, `"`)
parts := strings.Split(path, "/")
return parts[len(parts)-1]
}
parts := strings.Split(path, ".")
return parts[len(parts)-1]
}
func importPathToGoPath(path string) string {
trimmed := strings.TrimPrefix(path, "go.")
parts := strings.Split(trimmed, ".")
if len(parts) >= 3 && isDomainTLD(parts[1]) {
return parts[0] + "." + parts[1] + "/" + strings.Join(parts[2:], "/")
}
return strings.ReplaceAll(trimmed, ".", "/")
}
func isDomainTLD(segment string) bool {
switch segment {
case "com", "org", "net", "io", "dev", "app", "ai":
return true
default:
return false
}
}
func selectorPath(expr Expr) (string, bool) {
switch e := expr.(type) {
case IdentExpr:
return e.Name, true
case SelectorExpr:
left, ok := selectorPath(e.Receiver)
if !ok {
return "", false
}
return left + "." + e.Name, true
default:
return "", false
}
}
func (g *goGenerator) emitRunCatchingSupport() {
g.line("type gotlinResult struct {")
g.indentLevel++
g.line("exception any")
g.indentLevel--
g.line("}")
g.line("")
g.line("func gotlinRunCatching(fn func()) (result gotlinResult) {")
g.indentLevel++
g.line("defer func() {")
g.indentLevel++
g.line("if recovered := recover(); recovered != nil {")
g.indentLevel++
g.line("result.exception = recovered")
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}()")
g.line("fn()")
g.line("return")
g.indentLevel--
g.line("}")
g.line("")
g.line("func (r gotlinResult) isSuccess() bool {")
g.indentLevel++
g.line("return r.exception == nil")
g.indentLevel--
g.line("}")
g.line("")
g.line("func (r gotlinResult) exceptionOrNull() any {")
g.indentLevel++
g.line("return r.exception")
g.indentLevel--
g.line("}")
}
func (g *goGenerator) emitAutoThrowSupport() {
g.line("func gotlinAutoThrow[T any](value T, rest ...any) T {")
g.indentLevel++
g.line("if len(rest) == 1 {")
g.indentLevel++
g.line("if rest[0] == nil {")
g.indentLevel++
g.line("return value")
g.indentLevel--
g.line("}")
g.line("if err, ok := rest[0].(error); ok {")
g.indentLevel++
g.line("if err != nil {")
g.indentLevel++
g.line("panic(err)")
g.indentLevel--
g.line("}")
g.line("return value")
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}")
g.line("if len(rest) == 0 {")
g.indentLevel++
g.line("if any(value) == nil {")
g.indentLevel++
g.line("return value")
g.indentLevel--
g.line("}")
g.line("if err, ok := any(value).(error); ok {")
g.indentLevel++
g.line("if err != nil {")
g.indentLevel++
g.line("panic(err)")
g.indentLevel--
g.line("}")
g.line("var zero T")
g.line("return zero")
g.indentLevel--
g.line("}")
g.line("return value")
g.indentLevel--
g.line("}")
g.line(`panic("multi-value Go call requires explicit destructuring unless second value is error")`)
g.indentLevel--
g.line("}")
}
func (g *goGenerator) emitEveryMsSupport() {
g.line("func gotlinEveryMs(ms int) <-chan time.Time {")
g.indentLevel++
g.line("if ms <= 0 {")
g.indentLevel++
g.line(`panic("every(ms) requires ms > 0")`)
g.indentLevel--
g.line("}")
g.line("ticker := time.NewTicker(time.Duration(ms) * time.Millisecond)")
g.line("out := make(chan time.Time)")
g.line("go func() {")
g.indentLevel++
g.line("for value := range ticker.C {")
g.indentLevel++
g.line("out <- value")
g.indentLevel--
g.line("}")
g.indentLevel--
g.line("}()")
g.line("return out")
g.indentLevel--
g.line("}")
}
func (g *goGenerator) autoThrowValue(original Expr, rendered string) string {
if _, ok := original.(CallExpr); ok {
return "gotlinAutoThrow(" + rendered + ")"
}
return rendered
}
func parseIntLiteral(text string) (int, error) {
return strconv.Atoi(text)
}
func staticIntValue(expr Expr) (int, bool) {
switch e := expr.(type) {
case IntExpr:
parsed, err := parseIntLiteral(e.Value)
if err != nil {
return 0, false
}
return parsed, true
case UnaryExpr:
if e.Op != "-" {
return 0, false
}
inner, ok := staticIntValue(e.Value)
if !ok {
return 0, false
}
return -inner, true
default:
return 0, false
}
}
func parseFunctionType(text string) ([]string, string, bool) {
text = strings.TrimSpace(text)
if !strings.HasPrefix(text, "(") {
return nil, "", false
}
depth := 0
end := -1
for i, r := range text {
switch r {
case '(':
depth++
case ')':
depth--
if depth == 0 {
end = i
goto done
}
}
}
done:
if end == -1 {
return nil, "", false
}
rest := strings.TrimSpace(text[end+1:])
if !strings.HasPrefix(rest, "->") {
return nil, "", false
}
paramsText := strings.TrimSpace(text[1:end])
retText := strings.TrimSpace(strings.TrimPrefix(rest, "->"))
var params []string
if paramsText != "" {
params = splitTopLevel(paramsText, ',')
}
return params, retText, true
}
func splitTopLevel(text string, sep rune) []string {
var parts []string
depthParen := 0
depthAngle := 0
start := 0
for i, r := range text {
switch r {
case '(':
depthParen++
case ')':
depthParen--
case '<':
depthAngle++
case '>':
if depthAngle > 0 {
depthAngle--
}
default:
if r == sep && depthParen == 0 && depthAngle == 0 {
parts = append(parts, strings.TrimSpace(text[start:i]))
start = i + 1
}
}
}
parts = append(parts, strings.TrimSpace(text[start:]))
return parts
}
func parseGenericType(text string) (string, []string, bool) {
text = strings.TrimSpace(text)
start := strings.Index(text, "<")
if start <= 0 || !strings.HasSuffix(text, ">") {
return "", nil, false
}
depth := 0
end := -1
for i, r := range text {
switch r {
case '<':
depth++
case '>':
depth--
if depth == 0 {
end = i
}
}
}
if end != len(text)-1 || depth != 0 {
return "", nil, false
}
base := strings.TrimSpace(text[:start])
argsText := strings.TrimSpace(text[start+1 : end])
if base == "" || argsText == "" {
return "", nil, false
}
return base, splitTopLevel(argsText, ','), true
}
func lambdaHasValueReturn(stmts []Stmt) bool {
for _, stmt := range stmts {
switch s := stmt.(type) {
case ReturnStmt:
if s.Value != nil {
return true
}
case IfStmt:
if lambdaHasValueReturn(s.Then) || lambdaHasValueReturn(s.Else) {
return true
}
case WhileStmt:
if lambdaHasValueReturn(s.Body) {
return true
}
case SelectStmt:
for _, c := range s.Cases {
if lambdaHasValueReturn(c.Body) {
return true
}
}
}
}
return false
}
func min(a, b int) int {
if a < b {
return a
}
return b
}