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 }