package lang import ( "fmt" "strings" ) var coroutineBuiltins = map[string]bool{"runBlocking": true, "coroutineScope": true, "launch": true, "async": true, "delay": true, "withTimeout": true, "isActive": true} func programUsesCoroutines(program *Program) bool { for _, fn := range program.Functions { if fn.Suspend || statementsUseCoroutines(fn.Body) { return true } } for _, class := range program.Classes { for _, fn := range class.Methods { if fn.Suspend || statementsUseCoroutines(fn.Body) { return true } } } return false } func statementsUseCoroutines(stmts []Stmt) bool { for _, stmt := range stmts { if statementUsesCoroutines(stmt) { return true } } return false } func statementUsesCoroutines(stmt Stmt) bool { switch s := stmt.(type) { case VarDecl: return expressionUsesCoroutines(s.Value) case MultiVarDecl: return expressionUsesCoroutines(s.Value) case AssignStmt: return expressionUsesCoroutines(s.Value) case ExprStmt: return expressionUsesCoroutines(s.Value) case ReturnStmt: return s.Value != nil && expressionUsesCoroutines(s.Value) case IfStmt: return expressionUsesCoroutines(s.Cond) || statementsUseCoroutines(s.Then) || statementsUseCoroutines(s.Else) case WhileStmt: return expressionUsesCoroutines(s.Cond) || statementsUseCoroutines(s.Body) case ForEachStmt: return expressionUsesCoroutines(s.Source) || statementsUseCoroutines(s.Body) case TryCatchStmt: return statementsUseCoroutines(s.TryBody) || statementsUseCoroutines(s.CatchBody) } return false } func expressionUsesCoroutines(expr Expr) bool { switch e := expr.(type) { case CallExpr: if id, ok := e.Callee.(IdentExpr); ok && coroutineBuiltins[id.Name] { return true } for _, a := range e.Args { if expressionUsesCoroutines(a) { return true } } case LambdaExpr: return statementsUseCoroutines(e.Body) case SelectorExpr: return expressionUsesCoroutines(e.Receiver) case BinaryExpr: return expressionUsesCoroutines(e.Left) || expressionUsesCoroutines(e.Right) case UnaryExpr: return expressionUsesCoroutines(e.Value) } return false } func (g *goGenerator) emitCoroutineSupport() { g.line("type GotlinCoroutineScope struct { ctx context.Context; cancel context.CancelFunc; workers sync.WaitGroup; mutex sync.Mutex; failure any }") g.line("func gotlinNewCoroutineScope(parent context.Context) *GotlinCoroutineScope { ctx, cancel := context.WithCancel(parent); return &GotlinCoroutineScope{ctx: ctx, cancel: cancel} }") g.line("func (scope *GotlinCoroutineScope) fail(value any) { scope.mutex.Lock(); if scope.failure == nil { scope.failure = value; scope.cancel() }; scope.mutex.Unlock() }") g.line("func (scope *GotlinCoroutineScope) Launch(block func(*GotlinCoroutineScope)) { scope.workers.Add(1); go func(){ defer scope.workers.Done(); child:=gotlinNewCoroutineScope(scope.ctx); defer child.cancel(); defer func(){if value:=recover();value!=nil{scope.fail(value)}}(); block(child); child.wait() }() }") g.line("func (scope *GotlinCoroutineScope) wait() { scope.workers.Wait(); scope.mutex.Lock(); failure:=scope.failure; scope.mutex.Unlock(); if failure!=nil{panic(failure)} }") g.line("func (scope *GotlinCoroutineScope) Scope(block func(*GotlinCoroutineScope)) { child:=gotlinNewCoroutineScope(scope.ctx); defer child.cancel(); defer func(){if value:=recover();value!=nil{child.cancel();child.workers.Wait();panic(value)}}(); block(child); child.wait() }") g.line("func (scope *GotlinCoroutineScope) Delay(ms int) { timer:=time.NewTimer(time.Duration(ms)*time.Millisecond); defer timer.Stop(); select { case <-timer.C: case <-scope.ctx.Done(): panic(scope.ctx.Err()) } }") g.line("func (scope *GotlinCoroutineScope) IsActive() bool { return scope.ctx.Err()==nil }") g.line("func (scope *GotlinCoroutineScope) WithTimeout(ms int, block func(*GotlinCoroutineScope)) { ctx,cancel:=context.WithTimeout(scope.ctx,time.Duration(ms)*time.Millisecond); defer cancel(); child:=gotlinNewCoroutineScope(ctx); defer child.cancel(); block(child); child.wait() }") g.line("func gotlinRunBlocking(block func(*GotlinCoroutineScope)) { scope:=gotlinNewCoroutineScope(context.Background()); defer scope.cancel(); defer func(){if value:=recover();value!=nil{scope.cancel();scope.workers.Wait();panic(value)}}(); block(scope); scope.wait() }") g.line("type GotlinDeferred[T any] struct { done chan struct{}; value T; failure any }") g.line("func gotlinAsync[T any](scope *GotlinCoroutineScope, block func(*GotlinCoroutineScope) T) *GotlinDeferred[T] { deferred:=&GotlinDeferred[T]{done:make(chan struct{})}; scope.Launch(func(child *GotlinCoroutineScope){defer close(deferred.done);defer func(){if value:=recover();value!=nil{deferred.failure=value;scope.fail(value)}}();deferred.value=block(child)}); return deferred }") g.line("func (deferred *GotlinDeferred[T]) await() T { <-deferred.done; if deferred.failure!=nil{panic(deferred.failure)}; return deferred.value }") } func (g *goGenerator) coroutineLambda(lambda LambdaExpr, returnType string) (string, error) { var b strings.Builder b.WriteString("func(gotlinScope *GotlinCoroutineScope)") if mapped := mapGoType(returnType); mapped != "" { b.WriteString(" ") b.WriteString(mapped) } b.WriteString(" {\n") sub := goGenerator{indentLevel: 1, needsFmt: g.needsFmt, needsTime: g.needsTime, needsCoroutines: true, functions: g.functions, classes: g.classes, workers: g.workers, enums: g.enums, imports: g.imports, currentFunc: FunctionDecl{ReturnType: returnType}, currentClass: g.currentClass, currentWorker: g.currentWorker, currentCoroutineScope: "gotlinScope", mappings: g.mappings} sub.scopes = g.cloneScopes() sub.typeScopes = g.cloneTypeScopes() sub.pushScope() if err := sub.block(lambda.Body); err != nil { return "", err } b.Write(sub.buf.Bytes()) b.WriteString("}") return b.String(), nil } func coroutineLambdaArg(call CallExpr, name string) (LambdaExpr, error) { if len(call.Args) != 1 { return LambdaExpr{}, fmt.Errorf("%s expects one lambda", name) } lambda, ok := call.Args[0].(LambdaExpr) if !ok { return LambdaExpr{}, fmt.Errorf("%s expects a lambda", name) } return lambda, nil }