124 lines
6 KiB
Go
124 lines
6 KiB
Go
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
|
|
}
|