gotlin/internal/lang/coroutines.go

136 lines
6.9 KiB
Go

package lang
import (
"fmt"
"strings"
)
var coroutineBuiltins = map[string]bool{"runBlocking": true, "withContext": true, "coroutineScope": true, "launch": true, "async": true, "delay": true, "withTimeout": true, "isActive": true, "coroutineContext": true}
func programUsesCoroutines(program *Program) bool {
for _, fn := range program.Functions {
if statementsUseCoroutines(fn.Body) || (fn.ExpressionBody != nil && expressionUsesCoroutines(fn.ExpressionBody)) {
return true
}
}
for _, class := range program.Classes {
for _, fn := range class.Methods {
if statementsUseCoroutines(fn.Body) || (fn.ExpressionBody != nil && expressionUsesCoroutines(fn.ExpressionBody)) {
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 MatchExpr:
if expressionUsesCoroutines(e.Value) {
return true
}
for _, matchCase := range e.Cases {
if expressionUsesCoroutines(matchCase.Value) {
return true
}
}
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) Context() context.Context { return scope.ctx }")
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 (scope *GotlinCoroutineScope) WithContext(ctx context.Context, block func(*GotlinCoroutineScope)) { merged,cancel:=context.WithCancel(ctx); stop:=context.AfterFunc(scope.ctx,cancel); defer stop(); defer cancel(); child:=gotlinNewCoroutineScope(merged); 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("func gotlinRunWithContext(ctx context.Context, block func(*GotlinCoroutineScope)) { scope:=gotlinNewCoroutineScope(ctx); 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 := g.goType(returnType); mapped != "" {
b.WriteString(" ")
b.WriteString(mapped)
}
b.WriteString(" {\n")
sub := goGenerator{semantic: g.semantic, indentLevel: 1, needsFmt: g.needsFmt, needsTime: g.needsTime, needsCoroutines: true, currentFunc: FunctionDecl{ReturnType: returnType}, currentClass: g.currentClass, currentCoroutineScope: "gotlinScope"}
sub.scopes = g.cloneScopes()
sub.semanticScope = g.semanticScope
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
}