3138 lines
81 KiB
Go
3138 lines
81 KiB
Go
package lang
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/format"
|
|
"regexp"
|
|
"sort"
|
|
"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) {
|
|
semantic, err := Analyze(program)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g := goGenerator{semantic: semantic}
|
|
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 {
|
|
semantic *SemanticProgram
|
|
buf bytes.Buffer
|
|
indentLevel int
|
|
needsFmt bool
|
|
needsRunCatch bool
|
|
needsGoUnwrap bool
|
|
needsTime bool
|
|
needsJSONDecode bool
|
|
needsCoroutines bool
|
|
sqlContextAlias string
|
|
sqlPGXAlias string
|
|
currentFunc FunctionDecl
|
|
currentClass *ClassDecl
|
|
currentCoroutineScope string
|
|
scopes []map[string]bool
|
|
semanticScope *Scope
|
|
matchCounter int
|
|
resultCounter int
|
|
}
|
|
|
|
func (g *goGenerator) program(program *Program, packageOverride string) error {
|
|
containsSQL := programContainsSQL(program)
|
|
containsSQLExecution := programContainsSQLExecution(program)
|
|
g.needsCoroutines = programUsesCoroutines(program)
|
|
if g.needsCoroutines {
|
|
g.needsTime = true
|
|
}
|
|
if containsSQLExecution {
|
|
g.needsGoUnwrap = true
|
|
g.sqlContextAlias = runtimeImportAlias(program, "context", "gotlincontext")
|
|
g.sqlPGXAlias = runtimeImportAlias(program, "github.com/jackc/pgx/v5", "gotlinpgx")
|
|
}
|
|
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.needsRunCatch {
|
|
for _, fn := range program.Functions {
|
|
if usesRunCatching(fn.Body) {
|
|
g.needsRunCatch = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !g.needsGoUnwrap {
|
|
for _, fn := range program.Functions {
|
|
if usesGoUnwrap(fn.Body) {
|
|
g.needsGoUnwrap = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !g.needsGoUnwrap {
|
|
for _, class := range program.Classes {
|
|
for _, method := range class.Methods {
|
|
if usesGoUnwrap(method.Body) {
|
|
g.needsGoUnwrap = true
|
|
break
|
|
}
|
|
}
|
|
if g.needsGoUnwrap {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
runtimeImports := map[string]string{}
|
|
if containsSQLExecution {
|
|
runtimeImports["context"] = g.sqlContextAlias
|
|
runtimeImports["github.com/jackc/pgx/v5"] = g.sqlPGXAlias
|
|
}
|
|
if g.needsCoroutines {
|
|
runtimeImports["context"] = "context"
|
|
runtimeImports["sync"] = "sync"
|
|
}
|
|
imports := collectImports(program, g.needsFmt, g.needsTime, runtimeImports)
|
|
if len(program.Embeds) > 0 {
|
|
hasEmbed := false
|
|
for _, imp := range program.Imports {
|
|
if imp.Path == "embed" {
|
|
hasEmbed = true
|
|
break
|
|
}
|
|
}
|
|
if !hasEmbed {
|
|
imports = append(imports, `_ "embed"`)
|
|
sort.Strings(imports)
|
|
}
|
|
}
|
|
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.needsGoUnwrap {
|
|
g.emitGoUnwrapSupport()
|
|
g.line("")
|
|
}
|
|
if g.needsCoroutines {
|
|
g.emitCoroutineSupport()
|
|
g.line("")
|
|
}
|
|
g.line("type GotlinResult[T any] struct { Value T; Err error }")
|
|
g.line("func gotlinResultUnwrap[T any](result GotlinResult[T]) T { if result.Err != nil { panic(result.Err) }; return result.Value }")
|
|
g.line("func gotlinResultUnwrapOr[T any](result GotlinResult[T], fallback T) T { if result.Err != nil { return fallback }; return result.Value }")
|
|
g.line("")
|
|
for _, embedded := range program.Embeds {
|
|
g.line("//go:embed " + embedded.Path)
|
|
g.line("var " + embedded.Name + " " + g.goType(embedded.Type))
|
|
g.line("")
|
|
}
|
|
|
|
emitted := false
|
|
if containsSQL {
|
|
g.emitSQLSupport(program, containsSQLExecution)
|
|
emitted = true
|
|
}
|
|
for _, decl := range program.Interfaces {
|
|
if emitted {
|
|
g.line("")
|
|
}
|
|
g.interfaceDecl(decl)
|
|
emitted = true
|
|
}
|
|
for _, enum := range program.Enums {
|
|
if emitted {
|
|
g.line("")
|
|
}
|
|
g.enumDecl(enum)
|
|
emitted = true
|
|
}
|
|
for _, class := range program.Classes {
|
|
if emitted {
|
|
g.line("")
|
|
}
|
|
if err := g.classDecl(class); 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
|
|
}
|
|
for i := 0; i < len(g.semantic.Mappings.pairs); i++ {
|
|
g.line("")
|
|
if err := g.emitMapping(g.semantic.Mappings.pairs[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if g.needsJSONDecode {
|
|
if emitted {
|
|
g.line("")
|
|
}
|
|
g.emitJSONDecodeSupport()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *goGenerator) function(fn FunctionDecl) error {
|
|
g.currentClass = nil
|
|
g.currentFunc = fn
|
|
previousScope := g.currentCoroutineScope
|
|
if fn.Suspend {
|
|
g.currentCoroutineScope = "gotlinScope"
|
|
}
|
|
defer func() { g.currentCoroutineScope = previousScope }()
|
|
g.scopes = nil
|
|
g.semanticScope = nil
|
|
g.pushScope()
|
|
for _, param := range fn.Params {
|
|
g.defineType(param.Name, param.Type)
|
|
}
|
|
g.write("func ")
|
|
g.write(fn.Name)
|
|
g.write(renderGoTypeParameters(fn.TypeParams))
|
|
g.write(g.renderGoFunctionParams(fn))
|
|
if ret := g.goReturnType(fn.ReturnType); ret != "" {
|
|
g.write(" ")
|
|
g.write(ret)
|
|
}
|
|
g.write(" {\n")
|
|
g.indentLevel++
|
|
if err := g.block(fn.Body); err != nil {
|
|
return fmt.Errorf("function %s: %w", fn.Name, 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 {
|
|
prefix := ""
|
|
if method.Suspend {
|
|
prefix = "gotlinScope *GotlinCoroutineScope"
|
|
}
|
|
g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, prefix) + g.renderGoReturnSuffix(method.ReturnType))
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
}
|
|
|
|
func (g *goGenerator) enumDecl(decl EnumDecl) {
|
|
if enumIsString(decl) {
|
|
g.line("type " + decl.Name + " string")
|
|
g.line("const (")
|
|
g.indentLevel++
|
|
for _, variant := range decl.Variants {
|
|
g.line(decl.Name + variant.Name + " " + decl.Name + " = " + strconv.Quote(enumStringValue(variant)))
|
|
}
|
|
g.indentLevel--
|
|
g.line(")")
|
|
return
|
|
}
|
|
g.line("type " + decl.Name + " interface { is" + decl.Name + "() }")
|
|
for _, variant := range decl.Variants {
|
|
name := decl.Name + variant.Name
|
|
g.line("")
|
|
g.line("type " + name + " struct {")
|
|
g.indentLevel++
|
|
for i, typ := range variant.PayloadTypes {
|
|
g.line(fmt.Sprintf("Value%d %s", i, g.goType(typ)))
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("func (*" + name + ") is" + decl.Name + "() {}")
|
|
}
|
|
}
|
|
|
|
func enumIsString(decl EnumDecl) bool {
|
|
for _, variant := range decl.Variants {
|
|
if len(variant.PayloadTypes) > 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func enumStringValue(variant EnumVariant) string {
|
|
if variant.StringValue != "" {
|
|
return variant.StringValue
|
|
}
|
|
return variant.Name
|
|
}
|
|
|
|
func enumVariant(decl EnumDecl, name string) *EnumVariant {
|
|
for i := range decl.Variants {
|
|
if decl.Variants[i].Name == name {
|
|
return &decl.Variants[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *goGenerator) classDecl(class ClassDecl) error {
|
|
g.line("type " + class.Name + renderGoTypeParameters(class.TypeParams) + " struct {")
|
|
g.indentLevel++
|
|
for _, field := range class.Fields {
|
|
name := field.Name
|
|
tag := ""
|
|
if class.Data {
|
|
if field.Private {
|
|
tag = " `json:\"-\"`"
|
|
} else {
|
|
name = exportedGoName(name)
|
|
tag = fmt.Sprintf(" `json:\"%s\"`", jsonFieldName(field.Name, class.JSONNaming))
|
|
}
|
|
}
|
|
g.line(name + " " + g.goType(field.Type) + tag)
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.write("func New")
|
|
g.write(class.Name)
|
|
g.write(renderGoTypeParameters(class.TypeParams))
|
|
g.write("(")
|
|
for i, field := range class.Fields {
|
|
if i > 0 {
|
|
g.write(", ")
|
|
}
|
|
g.write(field.Name)
|
|
g.write(" ")
|
|
g.write(g.goType(field.Type))
|
|
}
|
|
g.write(") *")
|
|
g.write(class.Name)
|
|
g.write(renderGoTypeArguments(class.TypeParams))
|
|
g.write(" {\n")
|
|
g.indentLevel++
|
|
g.writeIndent()
|
|
g.write("return &")
|
|
g.write(class.Name)
|
|
g.write(renderGoTypeArguments(class.TypeParams))
|
|
g.write("{")
|
|
for i, field := range class.Fields {
|
|
if i > 0 {
|
|
g.write(", ")
|
|
}
|
|
if class.Data && !field.Private {
|
|
g.write(exportedGoName(field.Name))
|
|
} else {
|
|
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) method(class ClassDecl, fn FunctionDecl) error {
|
|
g.currentClass = &class
|
|
g.currentFunc = fn
|
|
previousScope := g.currentCoroutineScope
|
|
if fn.Suspend {
|
|
g.currentCoroutineScope = "gotlinScope"
|
|
}
|
|
defer func() { g.currentCoroutineScope = previousScope }()
|
|
g.scopes = nil
|
|
g.semanticScope = nil
|
|
g.pushScope()
|
|
g.define("self")
|
|
g.define("this")
|
|
for _, param := range fn.Params {
|
|
g.defineType(param.Name, param.Type)
|
|
}
|
|
|
|
g.write("func (self *")
|
|
g.write(class.Name)
|
|
g.write(renderGoTypeArguments(class.TypeParams))
|
|
g.write(") ")
|
|
g.write(fn.Name)
|
|
g.write(g.renderGoFunctionParams(fn))
|
|
if ret := g.goReturnType(fn.ReturnType); ret != "" {
|
|
g.write(" ")
|
|
g.write(ret)
|
|
}
|
|
g.write(" {\n")
|
|
g.indentLevel++
|
|
if err := g.block(fn.Body); err != nil {
|
|
return fmt.Errorf("method %s.%s: %w", class.Name, fn.Name, err)
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.popScope()
|
|
g.currentClass = nil
|
|
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 fmt.Errorf("%T: %w", stmt, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *goGenerator) tryVarDecl(decl VarDecl, attempt TryExpr, tail []Stmt) error {
|
|
base, resultArgs, ok := parseGenericType(g.currentFunc.ReturnType)
|
|
if !ok || base != "Result" || len(resultArgs) != 2 {
|
|
return fmt.Errorf("? can only be used inside a function returning Result<T, Error>")
|
|
}
|
|
g.resultCounter++
|
|
errorName := fmt.Sprintf("gotlinError%d", g.resultCounter)
|
|
valueName := decl.Name
|
|
if !nameUsedInStmts(decl.Name, tail) {
|
|
valueName = "_"
|
|
}
|
|
innerType := g.exprType(attempt.Value)
|
|
resolvedAttempt := exprMeta(attempt.Value)
|
|
rawGoCall := resolvedAttempt != nil && resolvedAttempt.Meaning == GoCallExpr
|
|
if innerBase, innerArgs, isResult := parseGenericType(innerType); isResult && innerBase == "Result" && len(innerArgs) == 2 && !rawGoCall {
|
|
resultName := fmt.Sprintf("gotlinResult%d", g.resultCounter)
|
|
value, err := g.expr(attempt.Value, innerType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.line(resultName + " := " + value)
|
|
g.line("if " + resultName + ".Err != nil { return GotlinResult[" + g.goType(resultArgs[0]) + "]{Err: " + resultName + ".Err} }")
|
|
g.line(valueName + " := " + resultName + ".Value")
|
|
if decl.Type == "" {
|
|
decl.Type = innerArgs[0]
|
|
}
|
|
} else {
|
|
value, err := g.expr(attempt.Value, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.line(valueName + ", " + errorName + " := " + value)
|
|
g.line("if " + errorName + " != nil { return GotlinResult[" + g.goType(resultArgs[0]) + "]{Err: " + errorName + "} }")
|
|
}
|
|
if valueName != "_" {
|
|
g.defineType(decl.Name, decl.Type)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *goGenerator) stmt(stmt Stmt, tail []Stmt) error {
|
|
switch s := stmt.(type) {
|
|
case VarDecl:
|
|
if _, isNull := s.Value.(NullExpr); isNull && s.Type == "" {
|
|
return fmt.Errorf("null requires an explicit nullable type")
|
|
}
|
|
if attempt, ok := s.Value.(TryExpr); ok {
|
|
return g.tryVarDecl(s, attempt, tail)
|
|
}
|
|
value, err := g.expr(s.Value, s.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value = g.passthroughValue(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, g.goType(s.Type), value))
|
|
}
|
|
typ := s.Type
|
|
if typ == "" {
|
|
typ = g.exprType(s.Value)
|
|
}
|
|
g.defineType(s.Name, typ)
|
|
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, g.lookupType(s.Name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value = g.passthroughValue(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.passthroughValue(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.passthroughValue(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 DeferStmt:
|
|
value, err := g.expr(s.Value, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.line("defer " + value)
|
|
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 name, thenNonNull, _ := nullableCondition(s.Cond); thenNonNull {
|
|
if typ := g.lookupType(name); strings.HasSuffix(typ, "?") {
|
|
g.defineType(name, strings.TrimSuffix(typ, "?"))
|
|
}
|
|
}
|
|
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 name, _, elseNonNull := nullableCondition(s.Cond); elseNonNull {
|
|
if typ := g.lookupType(name); strings.HasSuffix(typ, "?") {
|
|
g.defineType(name, strings.TrimSuffix(typ, "?"))
|
|
}
|
|
}
|
|
if err := g.block(s.Else); err != nil {
|
|
return err
|
|
}
|
|
g.popScope()
|
|
g.indentLevel--
|
|
g.writeIndent()
|
|
g.write("}")
|
|
}
|
|
g.write("\n")
|
|
if len(s.Else) == 0 && blockAlwaysTerminates(s.Then) {
|
|
if name, _, elseNonNull := nullableCondition(s.Cond); elseNonNull {
|
|
if typ := g.lookupType(name); strings.HasSuffix(typ, "?") {
|
|
g.defineType(name, strings.TrimSuffix(typ, "?"))
|
|
}
|
|
}
|
|
}
|
|
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 ForEachStmt:
|
|
source, err := g.expr(s.Source, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.line(fmt.Sprintf("for _, %s := range %s {", s.Name, source))
|
|
g.indentLevel++
|
|
g.pushScope()
|
|
g.define(s.Name)
|
|
if err := g.block(s.Body); err != nil {
|
|
return err
|
|
}
|
|
g.popScope()
|
|
g.indentLevel--
|
|
g.line("}")
|
|
case MatchStmt:
|
|
enumName := strings.TrimPrefix(g.exprType(s.Value), "*")
|
|
if enumName == "" && len(s.Cases) > 0 {
|
|
enumName = s.Cases[0].EnumName
|
|
}
|
|
decl := g.semantic.Enums[enumName]
|
|
value, err := g.expr(s.Value, enumName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.matchCounter++
|
|
matchName := fmt.Sprintf("gotlinMatch%d", g.matchCounter)
|
|
if enumIsString(decl) {
|
|
g.line("switch " + value + " {")
|
|
g.indentLevel++
|
|
for _, c := range s.Cases {
|
|
g.line("case " + enumName + c.VariantName + ":")
|
|
g.indentLevel++
|
|
g.pushScope()
|
|
if err := g.block(c.Body); err != nil {
|
|
return err
|
|
}
|
|
g.popScope()
|
|
g.indentLevel--
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
break
|
|
}
|
|
hasBindings := false
|
|
for _, c := range s.Cases {
|
|
if len(c.Bindings) > 0 {
|
|
hasBindings = true
|
|
break
|
|
}
|
|
}
|
|
if hasBindings {
|
|
g.line(fmt.Sprintf("switch %s := %s.(type) {", matchName, value))
|
|
} else {
|
|
g.line(fmt.Sprintf("switch %s.(type) {", value))
|
|
}
|
|
g.indentLevel++
|
|
for _, c := range s.Cases {
|
|
variant := enumVariant(decl, c.VariantName)
|
|
g.line("case *" + enumName + c.VariantName + ":")
|
|
g.indentLevel++
|
|
g.pushScope()
|
|
for i, binding := range c.Bindings {
|
|
g.line(fmt.Sprintf("%s := %s.Value%d", binding, matchName, i))
|
|
g.defineType(binding, variant.PayloadTypes[i])
|
|
}
|
|
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) {
|
|
resolved := exprMeta(expr)
|
|
resolvedSQL := false
|
|
if resolved != nil {
|
|
_, resolvedSQL = resolved.Node.(HIRSQL)
|
|
}
|
|
if resolved == nil || resolvedSQL {
|
|
if lowered, handled, err := g.lowerSQLQuery(expr); handled || err != nil {
|
|
return lowered, err
|
|
}
|
|
}
|
|
if expectedType != "" && expectedType != "Any" {
|
|
if actualType := g.exprType(expr); strings.HasSuffix(actualType, "?") && !strings.HasSuffix(expectedType, "?") {
|
|
return "", fmt.Errorf("nullable value of type %s cannot be used as non-nullable %s; use ?. or !!", actualType, expectedType)
|
|
}
|
|
}
|
|
switch e := expr.(type) {
|
|
case IdentExpr:
|
|
if g.currentClass != nil {
|
|
if e.Name == "this" {
|
|
return "self", nil
|
|
}
|
|
if field, found := classFieldByName(*g.currentClass, e.Name); found && !g.isDefined(e.Name) {
|
|
return "self." + mappingFieldName(*g.currentClass, field), nil
|
|
}
|
|
}
|
|
return e.Name, nil
|
|
case IntExpr:
|
|
return e.Value, nil
|
|
case FloatExpr:
|
|
return e.Value, nil
|
|
case StringExpr:
|
|
return e.Value, nil
|
|
case BoolExpr:
|
|
if e.Value {
|
|
return "true", nil
|
|
}
|
|
return "false", nil
|
|
case NullExpr:
|
|
if expectedType != "" && !strings.HasSuffix(expectedType, "?") {
|
|
return "", fmt.Errorf("null is not allowed for non-nullable type %s", expectedType)
|
|
}
|
|
return "nil", nil
|
|
case NonNullExpr:
|
|
sourceType := g.exprType(e.Value)
|
|
if !strings.HasSuffix(sourceType, "?") {
|
|
return "", fmt.Errorf("non-null assertion requires a nullable value, got %s", sourceType)
|
|
}
|
|
innerType := strings.TrimSuffix(sourceType, "?")
|
|
value, err := g.expr(e.Value, sourceType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
_, innerIsClass := g.classForType(innerType)
|
|
if innerIsClass || strings.HasPrefix(innerType, "*") {
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { panic(\"non-null assertion failed\") }; return value }(%s)", g.goType(sourceType), g.goType(innerType), value), nil
|
|
}
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { panic(\"non-null assertion failed\") }; return *value }(%s)", g.goType(sourceType), g.goType(innerType), value), nil
|
|
case TryExpr:
|
|
return "", fmt.Errorf("? propagation is only supported on variable declarations")
|
|
case UnaryExpr:
|
|
value, err := g.wrapExpr(e.Value, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return e.Op + value, nil
|
|
case BinaryExpr:
|
|
if e.Op == "==" || e.Op == "!=" {
|
|
if _, ok := e.Left.(NullExpr); ok {
|
|
if typ := g.exprType(e.Right); typ != "" && !strings.HasSuffix(typ, "?") {
|
|
return "", fmt.Errorf("null comparison requires a nullable value, got %s", typ)
|
|
}
|
|
}
|
|
if _, ok := e.Right.(NullExpr); ok {
|
|
if typ := g.exprType(e.Left); typ != "" && !strings.HasSuffix(typ, "?") {
|
|
return "", fmt.Errorf("null comparison requires a nullable value, got %s", typ)
|
|
}
|
|
}
|
|
}
|
|
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:
|
|
resolvedCall := exprMeta(e)
|
|
if resolvedCall == nil || resolvedCall.Meaning == EnumConstructionExpr {
|
|
selector, ok := e.Callee.(SelectorExpr)
|
|
if ok {
|
|
if receiver, ok := selector.Receiver.(IdentExpr); ok {
|
|
if receiver.Name == "Result" {
|
|
return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: selector.Name, Values: e.Args}, expectedType)
|
|
}
|
|
if _, ok := g.semantic.Enums[receiver.Name]; ok {
|
|
return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: selector.Name, Values: e.Args}, expectedType)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ident, ok := e.Callee.(IdentExpr); ok && coroutineBuiltins[ident.Name] {
|
|
switch ident.Name {
|
|
case "runBlocking":
|
|
lambda, err := coroutineLambdaArg(e, "runBlocking")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
block, err := g.coroutineLambda(lambda, "Unit")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "gotlinRunBlocking(" + block + ")", nil
|
|
case "coroutineScope", "launch":
|
|
if g.currentCoroutineScope == "" {
|
|
return "", fmt.Errorf("%s requires a coroutine scope", ident.Name)
|
|
}
|
|
lambda, err := coroutineLambdaArg(e, ident.Name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
block, err := g.coroutineLambda(lambda, "Unit")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
method := "Scope"
|
|
if ident.Name == "launch" {
|
|
method = "Launch"
|
|
}
|
|
return g.currentCoroutineScope + "." + method + "(" + block + ")", nil
|
|
case "async":
|
|
if g.currentCoroutineScope == "" {
|
|
return "", fmt.Errorf("async requires a coroutine scope")
|
|
}
|
|
if len(e.TypeArgs) != 1 {
|
|
return "", fmt.Errorf("async expects one result type")
|
|
}
|
|
lambda, err := coroutineLambdaArg(e, "async")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
block, err := g.coroutineLambda(lambda, e.TypeArgs[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "gotlinAsync[" + g.goType(e.TypeArgs[0]) + "](" + g.currentCoroutineScope + ", " + block + ")", nil
|
|
case "delay":
|
|
if g.currentCoroutineScope == "" || len(e.Args) != 1 {
|
|
return "", fmt.Errorf("delay(ms) requires a coroutine scope")
|
|
}
|
|
ms, err := g.expr(e.Args[0], "Int")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return g.currentCoroutineScope + ".Delay(" + ms + ")", nil
|
|
case "withTimeout":
|
|
if g.currentCoroutineScope == "" || len(e.Args) != 2 {
|
|
return "", fmt.Errorf("withTimeout expects milliseconds and a lambda")
|
|
}
|
|
ms, err := g.expr(e.Args[0], "Int")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
lambda, ok := e.Args[1].(LambdaExpr)
|
|
if !ok {
|
|
return "", fmt.Errorf("withTimeout expects a lambda")
|
|
}
|
|
block, err := g.coroutineLambda(lambda, "Unit")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return g.currentCoroutineScope + ".WithTimeout(" + ms + ", " + block + ")", nil
|
|
case "isActive":
|
|
if g.currentCoroutineScope == "" {
|
|
return "", fmt.Errorf("isActive requires a coroutine scope")
|
|
}
|
|
return g.currentCoroutineScope + ".IsActive()", nil
|
|
}
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "await" && len(e.Args) == 0 {
|
|
receiver, err := g.expr(selector.Receiver, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return receiver + ".await()", nil
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "unwrap" && len(e.Args) == 0 {
|
|
receiverType := g.exprType(selector.Receiver)
|
|
resolvedReceiver := exprMeta(selector.Receiver)
|
|
receiverExpected := receiverType
|
|
if resolvedReceiver != nil && resolvedReceiver.Meaning == GoCallExpr {
|
|
receiverExpected = ""
|
|
}
|
|
receiver, err := g.expr(selector.Receiver, receiverExpected)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if base, _, ok := parseGenericType(receiverType); ok && base == "Result" && (resolvedReceiver == nil || resolvedReceiver.Meaning != GoCallExpr) {
|
|
return "gotlinResultUnwrap(" + receiver + ")", nil
|
|
}
|
|
return "gotlinUnwrapGo(" + receiver + ")", nil
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "unwrapOr" && len(e.Args) == 1 {
|
|
receiverType := g.exprType(selector.Receiver)
|
|
base, args, ok := parseGenericType(receiverType)
|
|
if !ok || base != "Result" || len(args) != 2 {
|
|
return "", fmt.Errorf("unwrapOr requires a Result value")
|
|
}
|
|
receiver, err := g.expr(selector.Receiver, receiverType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fallback, err := g.expr(e.Args[0], args[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "gotlinResultUnwrapOr(" + receiver + ", " + fallback + ")", nil
|
|
}
|
|
if safe, ok := e.Callee.(SafeSelectorExpr); ok {
|
|
receiverType := g.exprType(safe.Receiver)
|
|
if !strings.HasSuffix(receiverType, "?") {
|
|
return "", fmt.Errorf("safe call requires a nullable receiver, got %s", receiverType)
|
|
}
|
|
innerType := strings.TrimSuffix(receiverType, "?")
|
|
class, ok := g.classForType(innerType)
|
|
if !ok {
|
|
return "", fmt.Errorf("safe call receiver %s is not a Gotlin class", receiverType)
|
|
}
|
|
var method *FunctionDecl
|
|
for i := range class.Methods {
|
|
if class.Methods[i].Name == safe.Name {
|
|
method = &class.Methods[i]
|
|
break
|
|
}
|
|
}
|
|
if method == nil {
|
|
return "", fmt.Errorf("unknown safe-call method %s.%s", class.Name, safe.Name)
|
|
}
|
|
if len(e.Args) != len(method.Params) {
|
|
return "", fmt.Errorf("%s.%s expects %d arguments", class.Name, safe.Name, len(method.Params))
|
|
}
|
|
receiver, err := g.expr(safe.Receiver, receiverType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
args := make([]string, 0, len(e.Args))
|
|
for i, arg := range e.Args {
|
|
value, err := g.expr(arg, method.Params[i].Type)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
args = append(args, value)
|
|
}
|
|
call := "value." + safe.Name + "(" + strings.Join(args, ", ") + ")"
|
|
if method.ReturnType == "Unit" {
|
|
return fmt.Sprintf("func(value %s) { if value != nil { %s } }(%s)", g.goType(receiverType), call, receiver), nil
|
|
}
|
|
_, returnsClass := g.classForType(method.ReturnType)
|
|
if returnsClass || strings.HasPrefix(method.ReturnType, "*") || strings.HasSuffix(method.ReturnType, "?") {
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return %s }(%s)", g.goType(receiverType), g.goType(nullableType(method.ReturnType)), call, receiver), nil
|
|
}
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; result := %s; return &result }(%s)", g.goType(receiverType), g.goType(nullableType(method.ReturnType)), call, receiver), nil
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "mapTo" && (resolvedCall == nil || resolvedCall.Meaning == MappingExpression) {
|
|
if len(e.TypeArgs) > 1 || len(e.Args) != 0 || len(e.NamedArgs) != 0 {
|
|
return "", fmt.Errorf("mapTo expects at most one type argument and no value arguments")
|
|
}
|
|
sourceType := g.exprType(selector.Receiver)
|
|
if sourceType == "" {
|
|
return "", fmt.Errorf("mapTo source requires a known type")
|
|
}
|
|
targetType := expectedType
|
|
if len(e.TypeArgs) == 1 {
|
|
targetType = g.mappingTopLevelTarget(e.TypeArgs[0])
|
|
}
|
|
if targetType == "" {
|
|
return "", fmt.Errorf("mapTo target type cannot be inferred; use mapTo<Target>() or provide a typed context")
|
|
}
|
|
function, err := g.ensureMapping(sourceType, targetType, strings.TrimPrefix(sourceType, "*"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
source, err := g.expr(selector.Receiver, sourceType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return function + "(" + source + ")", nil
|
|
}
|
|
if len(e.NamedArgs) > 0 {
|
|
callee, err := g.expr(e.Callee, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fields := make([]string, 0, len(e.NamedArgs))
|
|
for _, arg := range e.NamedArgs {
|
|
value, err := g.expr(arg.Value, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fields = append(fields, exportedGoFieldName(arg.Name)+": "+value)
|
|
}
|
|
return "&" + callee + "{" + strings.Join(fields, ", ") + "}", nil
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok {
|
|
if receiver, ok := selector.Receiver.(IdentExpr); ok && receiver.Name == "json" && selector.Name == "decode" {
|
|
if len(e.TypeArgs) != 1 || len(e.Args) != 1 {
|
|
return "", fmt.Errorf("json.decode expects one type argument and one body argument")
|
|
}
|
|
body, err := g.expr(e.Args[0], "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
g.needsJSONDecode = true
|
|
return fmt.Sprintf("gotlinJSONDecode[%s](%s)", g.goType(e.TypeArgs[0]), body), nil
|
|
}
|
|
}
|
|
if ident, ok := e.Callee.(IdentExpr); ok {
|
|
switch ident.Name {
|
|
case "goAssert":
|
|
if len(e.TypeArgs) != 1 || len(e.Args) != 1 {
|
|
return "", fmt.Errorf("goAssert<T>(value) expects one type and one value")
|
|
}
|
|
value, err := g.expr(e.Args[0], "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "(" + value + ").(" + g.goType(e.TypeArgs[0]) + ")", nil
|
|
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 "keys":
|
|
if len(e.Args) != 1 {
|
|
return "", fmt.Errorf("keys(map) expects exactly one argument")
|
|
}
|
|
_, mapTypes, ok := parseGenericType(g.exprType(e.Args[0]))
|
|
if !ok || len(mapTypes) != 2 {
|
|
return "", fmt.Errorf("keys(map) requires a typed map")
|
|
}
|
|
value, err := g.expr(e.Args[0], "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
keyType := g.goType(mapTypes[0])
|
|
mapType := g.goType(g.exprType(e.Args[0]))
|
|
return fmt.Sprintf("func(values %s) []%s { result := make([]%s, 0, len(values)); for key := range values { result = append(result, key) }; return result }(%s)", mapType, keyType, keyType, value), nil
|
|
}
|
|
}
|
|
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
|
|
}
|
|
if spread, ok := arg.(UnaryExpr); ok && spread.Op == "*" {
|
|
inner, err := g.expr(spread.Value, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
value = inner + "..."
|
|
}
|
|
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 == "ByteSlice" {
|
|
if len(args) != 1 {
|
|
return "", fmt.Errorf("ByteSlice expects exactly one argument")
|
|
}
|
|
if g.exprType(e.Args[0]) == "Int" {
|
|
return "make([]byte, " + args[0] + ")", nil
|
|
}
|
|
return "[]byte(" + args[0] + ")", 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.semantic.Classes[ident.Name]; ok && (resolvedCall == nil || resolvedCall.Meaning == ClassConstructionExpr) {
|
|
return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil
|
|
}
|
|
}
|
|
if ident, ok := e.Callee.(IdentExpr); ok {
|
|
if fn, found := g.semantic.Functions[ident.Name]; found && fn.Suspend {
|
|
if g.currentCoroutineScope == "" {
|
|
return "", fmt.Errorf("suspend function %s requires a coroutine scope", ident.Name)
|
|
}
|
|
args = append([]string{g.currentCoroutineScope}, args...)
|
|
}
|
|
}
|
|
if selector, ok := e.Callee.(SelectorExpr); ok {
|
|
if class, found := g.classForType(g.exprType(selector.Receiver)); found {
|
|
for _, method := range class.Methods {
|
|
if method.Name == selector.Name && method.Suspend {
|
|
if g.currentCoroutineScope == "" {
|
|
return "", fmt.Errorf("suspend method %s requires a coroutine scope", selector.Name)
|
|
}
|
|
args = append([]string{g.currentCoroutineScope}, args...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
callee, err := g.expr(e.Callee, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(e.TypeArgs) > 0 {
|
|
callee += g.renderCallTypeArguments(e.TypeArgs)
|
|
}
|
|
call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", "))
|
|
if result, ok := g.goErrorResult(e, call, expectedType); ok {
|
|
return result, nil
|
|
}
|
|
return call, nil
|
|
case SelectorExpr:
|
|
resolvedSelector := exprMeta(e)
|
|
if resolvedSelector == nil || resolvedSelector.Meaning == EnumReferenceExpr {
|
|
if receiver, ok := e.Receiver.(IdentExpr); ok {
|
|
if decl, ok := g.semantic.Enums[receiver.Name]; ok {
|
|
variant := enumVariant(decl, e.Name)
|
|
if variant == nil {
|
|
return "", fmt.Errorf("unknown variant %s.%s", receiver.Name, e.Name)
|
|
}
|
|
if len(variant.PayloadTypes) != 0 {
|
|
return "", fmt.Errorf("variant %s.%s requires %d values", receiver.Name, e.Name, len(variant.PayloadTypes))
|
|
}
|
|
return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: e.Name}, expectedType)
|
|
}
|
|
}
|
|
}
|
|
if receiverType := g.exprType(e.Receiver); strings.HasSuffix(receiverType, "?") {
|
|
return "", fmt.Errorf("nullable receiver %s requires ?. or !! before .%s", receiverType, e.Name)
|
|
}
|
|
receiver, err := g.expr(e.Receiver, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if call, ok := e.Receiver.(CallExpr); ok && len(call.NamedArgs) > 0 {
|
|
receiver = "(" + receiver + ")"
|
|
}
|
|
name := e.Name
|
|
if g.exprType(e.Receiver) == "GotlinSQLQuery" {
|
|
if name == "sql" {
|
|
name = "SQL"
|
|
}
|
|
if name == "args" {
|
|
name = "Args"
|
|
}
|
|
}
|
|
if ident, ok := e.Receiver.(IdentExpr); ok && g.semantic.Imports[ident.Name] {
|
|
name = exportedGoName(name)
|
|
} else if class, ok := g.classForType(g.exprType(e.Receiver)); ok {
|
|
for _, field := range class.Fields {
|
|
if field.Name == name && class.Data && !field.Private {
|
|
name = exportedGoName(name)
|
|
break
|
|
}
|
|
}
|
|
} else if ident, ok := e.Receiver.(IdentExpr); ok && (ident.Name == "this" || ident.Name == "self") && g.currentClass != nil {
|
|
for _, field := range g.currentClass.Fields {
|
|
if field.Name == name && g.currentClass.Data && !field.Private {
|
|
name = exportedGoName(name)
|
|
break
|
|
}
|
|
}
|
|
} else if typ := g.exprType(e.Receiver); typ == "" || strings.Contains(typ, ".") {
|
|
name = exportedGoName(name)
|
|
}
|
|
return fmt.Sprintf("%s.%s", receiver, name), nil
|
|
case SafeSelectorExpr:
|
|
receiverType := g.exprType(e.Receiver)
|
|
if !strings.HasSuffix(receiverType, "?") {
|
|
return "", fmt.Errorf("safe access requires a nullable receiver, got %s", receiverType)
|
|
}
|
|
innerType := strings.TrimSuffix(receiverType, "?")
|
|
class, ok := g.classForType(innerType)
|
|
if !ok {
|
|
return "", fmt.Errorf("safe access receiver %s is not a Gotlin class", receiverType)
|
|
}
|
|
field, ok := classFieldByName(class, e.Name)
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown field %s.%s", class.Name, e.Name)
|
|
}
|
|
receiver, err := g.expr(e.Receiver, receiverType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fieldName := mappingFieldName(class, field)
|
|
_, fieldIsClass := g.classForType(field.Type)
|
|
if fieldIsClass || strings.HasPrefix(field.Type, "*") || strings.HasSuffix(field.Type, "?") {
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return value.%s }(%s)", g.goType(receiverType), g.goType(nullableType(field.Type)), fieldName, receiver), nil
|
|
}
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; result := value.%s; return &result }(%s)", g.goType(receiverType), g.goType(nullableType(field.Type)), fieldName, receiver), nil
|
|
case IndexExpr:
|
|
receiver, err := g.expr(e.Receiver, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
index, err := g.expr(e.Index, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return receiver + "[" + index + "]", nil
|
|
case MatchExpr:
|
|
return g.valueMatch(e, expectedType)
|
|
case EnumVariantExpr:
|
|
if e.EnumName == "Result" {
|
|
base, args, ok := parseGenericType(expectedType)
|
|
if !ok || base != "Result" || len(args) != 2 {
|
|
return "", fmt.Errorf("Result.%s requires an expected Result<T, Error> type", e.VariantName)
|
|
}
|
|
if e.VariantName == "Ok" {
|
|
if len(e.Values) != 1 {
|
|
return "", fmt.Errorf("Result.Ok expects one value")
|
|
}
|
|
value, err := g.expr(e.Values[0], args[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("GotlinResult[%s]{Value: %s}", g.goType(args[0]), value), nil
|
|
}
|
|
if e.VariantName == "Err" {
|
|
if len(e.Values) != 1 {
|
|
return "", fmt.Errorf("Result.Err expects one error")
|
|
}
|
|
value, err := g.expr(e.Values[0], "Error")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("GotlinResult[%s]{Err: %s}", g.goType(args[0]), value), nil
|
|
}
|
|
return "", fmt.Errorf("unknown Result variant %s", e.VariantName)
|
|
}
|
|
decl, ok := g.semantic.Enums[e.EnumName]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown enum %s", e.EnumName)
|
|
}
|
|
var variant *EnumVariant
|
|
for i := range decl.Variants {
|
|
if decl.Variants[i].Name == e.VariantName {
|
|
variant = &decl.Variants[i]
|
|
break
|
|
}
|
|
}
|
|
if variant == nil {
|
|
return "", fmt.Errorf("unknown variant %s.%s", e.EnumName, e.VariantName)
|
|
}
|
|
if len(e.Values) != len(variant.PayloadTypes) {
|
|
return "", fmt.Errorf("variant %s.%s expects %d values", e.EnumName, e.VariantName, len(variant.PayloadTypes))
|
|
}
|
|
if enumIsString(decl) {
|
|
return e.EnumName + e.VariantName, nil
|
|
}
|
|
fields := make([]string, 0, len(e.Values))
|
|
for i, valueExpr := range e.Values {
|
|
value, err := g.expr(valueExpr, variant.PayloadTypes[i])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fields = append(fields, fmt.Sprintf("Value%d: %s", i, value))
|
|
}
|
|
return "&" + e.EnumName + e.VariantName + "{" + strings.Join(fields, ", ") + "}", nil
|
|
case LambdaExpr:
|
|
return g.lambda(e, expectedType)
|
|
default:
|
|
return "", fmt.Errorf("unsupported expression %T", expr)
|
|
}
|
|
}
|
|
|
|
func (g *goGenerator) valueMatch(match MatchExpr, expectedType string) (string, error) {
|
|
enumName := strings.TrimPrefix(g.exprType(match.Value), "*")
|
|
if enumName == "" && len(match.Cases) > 0 {
|
|
enumName = match.Cases[0].EnumName
|
|
}
|
|
decl := g.semantic.Enums[enumName]
|
|
resultType := expectedType
|
|
if resultType == "" || resultType == "Any" {
|
|
resultType = g.exprType(match)
|
|
}
|
|
value, err := g.expr(match.Value, enumName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
g.matchCounter++
|
|
matchName := fmt.Sprintf("gotlinMatch%d", g.matchCounter)
|
|
var out strings.Builder
|
|
out.WriteString("func() ")
|
|
out.WriteString(g.goType(resultType))
|
|
out.WriteString(" { ")
|
|
if enumIsString(decl) {
|
|
out.WriteString("switch ")
|
|
out.WriteString(value)
|
|
out.WriteString(" { ")
|
|
for _, c := range match.Cases {
|
|
arm, err := g.expr(c.Value, resultType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
out.WriteString("case ")
|
|
out.WriteString(enumName + c.VariantName)
|
|
out.WriteString(": return ")
|
|
out.WriteString(arm)
|
|
out.WriteString("; ")
|
|
}
|
|
out.WriteString("}; ")
|
|
} else {
|
|
out.WriteString("switch ")
|
|
out.WriteString(matchName)
|
|
out.WriteString(" := ")
|
|
out.WriteString(value)
|
|
out.WriteString(".(type) { ")
|
|
for _, c := range match.Cases {
|
|
variant := enumVariant(decl, c.VariantName)
|
|
g.pushScope()
|
|
out.WriteString("case *")
|
|
out.WriteString(enumName + c.VariantName)
|
|
out.WriteString(": ")
|
|
for i, binding := range c.Bindings {
|
|
g.defineType(binding, variant.PayloadTypes[i])
|
|
out.WriteString(binding)
|
|
out.WriteString(" := ")
|
|
out.WriteString(matchName)
|
|
out.WriteString(fmt.Sprintf(".Value%d; ", i))
|
|
}
|
|
arm, err := g.expr(c.Value, resultType)
|
|
g.popScope()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
out.WriteString("return ")
|
|
out.WriteString(arm)
|
|
out.WriteString("; ")
|
|
}
|
|
out.WriteString("}; ")
|
|
}
|
|
out.WriteString(`panic("unreachable exhaustive match") }()`)
|
|
return out.String(), nil
|
|
}
|
|
|
|
func (g *goGenerator) emitJSONDecodeSupport() {
|
|
g.line("func gotlinJSONDecode[T any](body []byte) GotlinResult[T] {")
|
|
g.indentLevel++
|
|
g.line("var value T")
|
|
g.line("if err := json.Unmarshal(body, &value); err != nil { return GotlinResult[T]{Err: err} }")
|
|
g.line("return GotlinResult[T]{Value: value}")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
}
|
|
|
|
func (g *goGenerator) emitSQLSupport(program *Program, execution bool) {
|
|
g.line("type GotlinSQLQuery struct {")
|
|
g.indentLevel++
|
|
g.line("SQL string")
|
|
g.line("Args []any")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
if !execution {
|
|
return
|
|
}
|
|
g.line("")
|
|
g.line("type gotlinSQLQuerier interface {")
|
|
g.indentLevel++
|
|
g.line("Query(" + g.sqlContextAlias + ".Context, string, ...any) (" + g.sqlPGXAlias + ".Rows, error)")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("type gotlinSQLRow interface {")
|
|
g.indentLevel++
|
|
g.line("Scan(...any) error")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("type gotlinSQLScanner[T any] func(gotlinSQLRow) (*T, error)")
|
|
g.line("")
|
|
g.line("type gotlinSQLError string")
|
|
g.line("")
|
|
g.line("func (err gotlinSQLError) Error() string {")
|
|
g.indentLevel++
|
|
g.line("return string(err)")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func gotlinSQLFetch[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[[]*T] {")
|
|
g.indentLevel++
|
|
g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)")
|
|
g.line("if err != nil { return GotlinResult[[]*T]{Err: err} }")
|
|
g.line("defer rows.Close()")
|
|
g.line("values := make([]*T, 0)")
|
|
g.line("for rows.Next() {")
|
|
g.indentLevel++
|
|
g.line("value, err := scan(rows)")
|
|
g.line("if err != nil { return GotlinResult[[]*T]{Err: err} }")
|
|
g.line("values = append(values, value)")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("if err := rows.Err(); err != nil { return GotlinResult[[]*T]{Err: err} }")
|
|
g.line("return GotlinResult[[]*T]{Value: values}")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func gotlinSQLSingle[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[*T] {")
|
|
g.indentLevel++
|
|
g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)")
|
|
g.line("if err != nil { return GotlinResult[*T]{Err: err} }")
|
|
g.line("defer rows.Close()")
|
|
g.line("if !rows.Next() {")
|
|
g.indentLevel++
|
|
g.line("if err := rows.Err(); err != nil { return GotlinResult[*T]{Err: err} }")
|
|
g.line(`return GotlinResult[*T]{Err: gotlinSQLError("SQL single() expected exactly one row, got zero")}`)
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("value, err := scan(rows)")
|
|
g.line("if err != nil { return GotlinResult[*T]{Err: err} }")
|
|
g.line("if rows.Next() {")
|
|
g.indentLevel++
|
|
g.line(`return GotlinResult[*T]{Err: gotlinSQLError("SQL single() expected exactly one row, got more than one")}`)
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("if err := rows.Err(); err != nil { return GotlinResult[*T]{Err: err} }")
|
|
g.line("return GotlinResult[*T]{Value: value}")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("type GotlinSQLIterator[T any] struct {")
|
|
g.indentLevel++
|
|
g.line("rows " + g.sqlPGXAlias + ".Rows")
|
|
g.line("scan gotlinSQLScanner[T]")
|
|
g.line("current *T")
|
|
g.line("streamErr error")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func gotlinSQLIterate[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[*GotlinSQLIterator[T]] {")
|
|
g.indentLevel++
|
|
g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)")
|
|
g.line("if err != nil { return GotlinResult[*GotlinSQLIterator[T]]{Err: err} }")
|
|
g.line("return GotlinResult[*GotlinSQLIterator[T]]{Value: &GotlinSQLIterator[T]{rows: rows, scan: scan}}")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func (iterator *GotlinSQLIterator[T]) next() bool {")
|
|
g.indentLevel++
|
|
g.line("iterator.current = nil")
|
|
g.line("if iterator.rows == nil || !iterator.rows.Next() {")
|
|
g.indentLevel++
|
|
g.line("if iterator.rows != nil {")
|
|
g.indentLevel++
|
|
g.line("iterator.streamErr = iterator.rows.Err()")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("return false")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("value, err := iterator.scan(iterator.rows)")
|
|
g.line("if err != nil {")
|
|
g.indentLevel++
|
|
g.line("iterator.streamErr = err")
|
|
g.line("return false")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("iterator.current = value")
|
|
g.line("return true")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func (iterator *GotlinSQLIterator[T]) value() *T {")
|
|
g.indentLevel++
|
|
g.line("if iterator.current == nil {")
|
|
g.indentLevel++
|
|
g.line(`panic("SQL iterator value() requires a successful next()")`)
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("return iterator.current")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func (iterator *GotlinSQLIterator[T]) close() {")
|
|
g.indentLevel++
|
|
g.line("if iterator.rows != nil {")
|
|
g.indentLevel++
|
|
g.line("iterator.rows.Close()")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("")
|
|
g.line("func (iterator *GotlinSQLIterator[T]) err() error {")
|
|
g.indentLevel++
|
|
g.line("if iterator.streamErr != nil {")
|
|
g.indentLevel++
|
|
g.line("return iterator.streamErr")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("if iterator.rows != nil {")
|
|
g.indentLevel++
|
|
g.line("return iterator.rows.Err()")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line("return nil")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
|
|
for _, class := range program.Classes {
|
|
if !class.Data {
|
|
continue
|
|
}
|
|
g.line("")
|
|
g.line("func gotlinSQLScan" + class.Name + "(row gotlinSQLRow) (*" + class.Name + ", error) {")
|
|
g.indentLevel++
|
|
g.line("value := new(" + class.Name + ")")
|
|
destinations := make([]string, 0, len(class.Fields))
|
|
for _, field := range class.Fields {
|
|
name := field.Name
|
|
if !field.Private {
|
|
name = exportedGoName(name)
|
|
}
|
|
destinations = append(destinations, "&value."+name)
|
|
}
|
|
g.line("err := row.Scan(" + strings.Join(destinations, ", ") + ")")
|
|
g.line("return value, err")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
}
|
|
}
|
|
|
|
func exportedGoName(name string) string {
|
|
if name == "" || name[0] < 'a' || name[0] > 'z' {
|
|
return name
|
|
}
|
|
return strings.ToUpper(name[:1]) + name[1:]
|
|
}
|
|
|
|
func exportedGoFieldName(name string) string {
|
|
name = exportedGoName(name)
|
|
for _, pair := range [][2]string{{"Jwks", "JWKS"}, {"Jwt", "JWT"}, {"Url", "URL"}, {"Api", "API"}, {"Sql", "SQL"}, {"Id", "ID"}} {
|
|
name = strings.ReplaceAll(name, pair[0], pair[1])
|
|
}
|
|
return name
|
|
}
|
|
|
|
func snakeCase(name string) string {
|
|
var b strings.Builder
|
|
for i, r := range name {
|
|
if i > 0 && r >= 'A' && r <= 'Z' {
|
|
b.WriteByte('_')
|
|
}
|
|
b.WriteRune(rune(strings.ToLower(string(r))[0]))
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func jsonFieldName(name, policy string) string {
|
|
switch policy {
|
|
case "", "snakeCase":
|
|
return snakeCase(name)
|
|
case "camelCase":
|
|
if name == "" {
|
|
return name
|
|
}
|
|
return strings.ToLower(name[:1]) + name[1:]
|
|
case "pascalCase":
|
|
return exportedGoName(name)
|
|
case "kebabCase":
|
|
return strings.ReplaceAll(snakeCase(name), "_", "-")
|
|
default:
|
|
return snakeCase(name)
|
|
}
|
|
}
|
|
|
|
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 = g.goType(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 = g.goType(keyType)
|
|
}
|
|
if valType != "" {
|
|
goValType = g.goType(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 = g.goType(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) 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 (g *goGenerator) goType(name string) string { return g.semantic.GoType(name) }
|
|
|
|
func nullableType(name string) string {
|
|
if strings.HasSuffix(name, "?") {
|
|
return name
|
|
}
|
|
return name + "?"
|
|
}
|
|
|
|
func nullableCondition(expr Expr) (name string, thenNonNull bool, elseNonNull bool) {
|
|
binary, ok := expr.(BinaryExpr)
|
|
if !ok || (binary.Op != "==" && binary.Op != "!=") {
|
|
return "", false, false
|
|
}
|
|
ident, leftIdent := binary.Left.(IdentExpr)
|
|
_, rightNull := binary.Right.(NullExpr)
|
|
if !leftIdent || !rightNull {
|
|
ident, leftIdent = binary.Right.(IdentExpr)
|
|
_, rightNull = binary.Left.(NullExpr)
|
|
}
|
|
if !leftIdent || !rightNull {
|
|
return "", false, false
|
|
}
|
|
if binary.Op == "!=" {
|
|
return ident.Name, true, false
|
|
}
|
|
return ident.Name, false, true
|
|
}
|
|
|
|
func blockAlwaysTerminates(stmts []Stmt) bool {
|
|
if len(stmts) == 0 {
|
|
return false
|
|
}
|
|
switch last := stmts[len(stmts)-1].(type) {
|
|
case ReturnStmt, ThrowStmt:
|
|
return true
|
|
case IfStmt:
|
|
return len(last.Else) > 0 && blockAlwaysTerminates(last.Then) && blockAlwaysTerminates(last.Else)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (g *goGenerator) goReturnType(name string) string {
|
|
return g.goType(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 DeferStmt:
|
|
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 ForEachStmt:
|
|
if exprUsesPrintln(s.Source) || usesPrintln(s.Body) {
|
|
return true
|
|
}
|
|
case MatchStmt:
|
|
if exprUsesPrintln(s.Value) {
|
|
return true
|
|
}
|
|
for _, c := range s.Cases {
|
|
if 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 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 TryCatchStmt:
|
|
if usesRunCatching(s.TryBody) || usesRunCatching(s.CatchBody) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func usesGoUnwrap(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 usesGoUnwrap(s.Then) || usesGoUnwrap(s.Else) {
|
|
return true
|
|
}
|
|
case WhileStmt:
|
|
if usesGoUnwrap(s.Body) {
|
|
return true
|
|
}
|
|
case TryCatchStmt:
|
|
if usesGoUnwrap(s.TryBody) || usesGoUnwrap(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(g.goType(param.Type))
|
|
}
|
|
b.WriteString(")")
|
|
if goRet := g.goType(returnType); goRet != "" {
|
|
b.WriteString(" ")
|
|
b.WriteString(goRet)
|
|
}
|
|
b.WriteString(" {\n")
|
|
|
|
sub := goGenerator{
|
|
indentLevel: 1,
|
|
needsFmt: g.needsFmt,
|
|
semantic: g.semantic,
|
|
currentFunc: FunctionDecl{ReturnType: returnType},
|
|
currentClass: g.currentClass,
|
|
}
|
|
if g.currentClass != nil {
|
|
classCopy := *g.currentClass
|
|
sub.currentClass = &classCopy
|
|
}
|
|
sub.scopes = g.cloneScopes()
|
|
sub.semanticScope = g.semanticScope
|
|
sub.pushScope()
|
|
if lambda.ImplicitIt {
|
|
if len(params) == 1 {
|
|
sub.defineType("it", params[0].Type)
|
|
} else {
|
|
sub.define("it")
|
|
}
|
|
}
|
|
for _, param := range params {
|
|
sub.defineType(param.Name, param.Type)
|
|
}
|
|
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.semantic.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.semantic.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"}
|
|
case "sort.Slice", "sort.slice":
|
|
return []string{"Any", "(Int, Int) -> Boolean"}
|
|
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 DeferStmt:
|
|
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 ForEachStmt:
|
|
if exprUsesName(s.Source, name, localShadowed) {
|
|
return true
|
|
}
|
|
if nameUsedInStmtsWithShadow(name, s.Body, localShadowed || s.Name == name) {
|
|
return true
|
|
}
|
|
case MatchStmt:
|
|
if exprUsesName(s.Value, name, localShadowed) {
|
|
return true
|
|
}
|
|
for _, c := range s.Cases {
|
|
caseShadowed := localShadowed
|
|
for _, binding := range c.Bindings {
|
|
if binding == name {
|
|
caseShadowed = true
|
|
}
|
|
}
|
|
if nameUsedInStmtsWithShadow(name, c.Body, caseShadowed) {
|
|
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 NonNullExpr:
|
|
return exprUsesName(e.Value, name, shadowed)
|
|
case TryExpr:
|
|
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 IndexExpr:
|
|
return exprUsesName(e.Receiver, name, shadowed) || exprUsesName(e.Index, name, shadowed)
|
|
case EnumVariantExpr:
|
|
for _, value := range e.Values {
|
|
if exprUsesName(value, name, shadowed) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
case MatchExpr:
|
|
if exprUsesName(e.Value, name, shadowed) {
|
|
return true
|
|
}
|
|
for _, matchCase := range e.Cases {
|
|
caseShadowed := shadowed
|
|
for _, binding := range matchCase.Bindings {
|
|
if binding == name {
|
|
caseShadowed = true
|
|
}
|
|
}
|
|
if exprUsesName(matchCase.Value, name, caseShadowed) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
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{})
|
|
g.semanticScope = NewScope(g.semanticScope)
|
|
}
|
|
|
|
func (g *goGenerator) popScope() {
|
|
if len(g.scopes) == 0 {
|
|
return
|
|
}
|
|
g.scopes = g.scopes[:len(g.scopes)-1]
|
|
g.semanticScope = g.semanticScope.Parent
|
|
}
|
|
|
|
func (g *goGenerator) define(name string) {
|
|
g.defineType(name, "")
|
|
}
|
|
|
|
func (g *goGenerator) defineType(name, typ string) {
|
|
if len(g.scopes) == 0 {
|
|
g.pushScope()
|
|
}
|
|
g.scopes[len(g.scopes)-1][name] = true
|
|
resolved, err := g.semantic.ResolveType(typ)
|
|
if err != nil {
|
|
resolved = UnknownType{}
|
|
}
|
|
g.semanticScope.Symbols[name] = &Symbol{Name: name, Kind: VariableSymbol, Type: resolved}
|
|
}
|
|
|
|
func (g *goGenerator) lookupType(name string) string {
|
|
if g.semanticScope != nil {
|
|
if symbol, ok := g.semanticScope.Lookup(name); ok {
|
|
if isUnknownType(symbol.Type) {
|
|
return ""
|
|
}
|
|
return symbol.Type.String()
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (g *goGenerator) exprType(expr Expr) string {
|
|
if ident, ok := expr.(IdentExpr); ok {
|
|
if typ := g.lookupType(ident.Name); typ != "" {
|
|
return typ
|
|
}
|
|
}
|
|
if resolved := exprMeta(expr); resolved != nil && !isUnknownType(resolved.Type) {
|
|
return resolved.Type.String()
|
|
}
|
|
// Generator-created expressions do not pass through semantic analysis.
|
|
typ := g.semantic.TypeOf(expr, TypeEnvironment{Scope: g.semanticScope, Class: g.currentClassSymbol()})
|
|
if isUnknownType(typ) {
|
|
return ""
|
|
}
|
|
return typ.String()
|
|
}
|
|
|
|
func (g *goGenerator) currentClassSymbol() *ClassSymbol {
|
|
if g.currentClass == nil {
|
|
return nil
|
|
}
|
|
return g.semantic.ClassInfo[g.currentClass.Name]
|
|
}
|
|
|
|
func (g *goGenerator) goErrorResult(call CallExpr, rendered, expectedType string) (string, bool) {
|
|
base, args, ok := parseGenericType(expectedType)
|
|
resolved := exprMeta(call)
|
|
external := false
|
|
if resolved != nil {
|
|
_, external = resolved.Node.(HIRGoCall)
|
|
}
|
|
if !external {
|
|
external = g.isExternalGoCall(call.Callee)
|
|
}
|
|
if !ok || base != "Result" || len(args) != 2 || args[1] != "Error" || !external {
|
|
return "", false
|
|
}
|
|
if actualBase, _, actualIsResult := parseGenericType(g.exprType(call)); actualIsResult && actualBase == "Result" && (resolved == nil || resolved.Meaning != GoCallExpr) {
|
|
return "", false
|
|
}
|
|
valueType := g.goType(args[0])
|
|
if args[0] == "Unit" {
|
|
valueType = "struct{}"
|
|
return fmt.Sprintf("func() GotlinResult[%s] { err := %s; return GotlinResult[%s]{Err: err} }()", valueType, rendered, valueType), true
|
|
}
|
|
return fmt.Sprintf("func() GotlinResult[%s] { value, err := %s; return GotlinResult[%s]{Value: value, Err: err} }()", valueType, rendered, valueType), true
|
|
}
|
|
|
|
func (g *goGenerator) isExternalGoCall(callee Expr) bool {
|
|
switch value := callee.(type) {
|
|
case IdentExpr:
|
|
if _, ok := g.semantic.Functions[value.Name]; ok {
|
|
return false
|
|
}
|
|
if _, ok := g.semantic.Classes[value.Name]; ok {
|
|
return false
|
|
}
|
|
return true
|
|
case SelectorExpr:
|
|
if receiver, ok := value.Receiver.(IdentExpr); ok && g.semantic.Imports[receiver.Name] {
|
|
return true
|
|
}
|
|
if _, ok := g.classForType(g.exprType(value.Receiver)); ok {
|
|
return false
|
|
}
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (g *goGenerator) classForType(typ string) (ClassDecl, bool) {
|
|
typ = strings.TrimPrefix(typ, "*")
|
|
typ = strings.TrimSuffix(typ, "?")
|
|
class, ok := g.semantic.Classes[typ]
|
|
return class, ok
|
|
}
|
|
|
|
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) assignTarget(name string) string {
|
|
if g.currentClass != nil && !g.isDefined(name) {
|
|
if field, found := classFieldByName(*g.currentClass, name); found {
|
|
return "self." + mappingFieldName(*g.currentClass, field)
|
|
}
|
|
}
|
|
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 (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
|
|
prefix := ""
|
|
if function.Suspend {
|
|
prefix = "gotlinScope *GotlinCoroutineScope"
|
|
}
|
|
return g.renderGoParamsWithPrefix(function.Params, prefix)
|
|
}
|
|
|
|
func renderGoTypeParameters(params []string) string {
|
|
if len(params) == 0 {
|
|
return ""
|
|
}
|
|
values := make([]string, len(params))
|
|
for index, param := range params {
|
|
values[index] = param + " any"
|
|
}
|
|
return "[" + strings.Join(values, ", ") + "]"
|
|
}
|
|
|
|
func renderGoTypeArguments(params []string) string {
|
|
if len(params) == 0 {
|
|
return ""
|
|
}
|
|
return "[" + strings.Join(params, ", ") + "]"
|
|
}
|
|
|
|
func (g *goGenerator) renderCallTypeArguments(args []string) string {
|
|
if len(args) == 0 {
|
|
return ""
|
|
}
|
|
values := make([]string, len(args))
|
|
for index, argument := range args {
|
|
values[index] = g.goType(argument)
|
|
}
|
|
return "[" + strings.Join(values, ", ") + "]"
|
|
}
|
|
|
|
func (g *goGenerator) renderGoParamsWithPrefix(params []Param, prefix string) string {
|
|
var b strings.Builder
|
|
b.WriteString("(")
|
|
if prefix != "" {
|
|
b.WriteString(prefix)
|
|
if len(params) > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
}
|
|
for i, param := range params {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(param.Name)
|
|
b.WriteString(" ")
|
|
b.WriteString(g.goType(param.Type))
|
|
}
|
|
b.WriteString(")")
|
|
return b.String()
|
|
}
|
|
|
|
func (g *goGenerator) renderGoReturnSuffix(returnType string) string {
|
|
if ret := g.goReturnType(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 MatchExpr:
|
|
if exprUsesPrintln(e.Value) {
|
|
return true
|
|
}
|
|
for _, matchCase := range e.Cases {
|
|
if exprUsesPrintln(matchCase.Value) {
|
|
return true
|
|
}
|
|
}
|
|
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 MatchExpr:
|
|
if exprUsesRunCatching(e.Value) {
|
|
return true
|
|
}
|
|
for _, matchCase := range e.Cases {
|
|
if exprUsesRunCatching(matchCase.Value) {
|
|
return true
|
|
}
|
|
}
|
|
case LambdaExpr:
|
|
return usesRunCatching(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, runtimeImports map[string]string) []string {
|
|
seen := map[string]bool{}
|
|
runtimePaths := 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 path, alias := range runtimeImports {
|
|
rendered := alias + ` "` + path + `"`
|
|
seen[rendered] = true
|
|
runtimePaths[path] = true
|
|
imports = append(imports, rendered)
|
|
}
|
|
for _, imp := range program.Imports {
|
|
path := imp.Path
|
|
goPath := path
|
|
if !strings.HasPrefix(path, `"`) {
|
|
goPath = importPathToGoPath(path)
|
|
path = `"` + goPath + `"`
|
|
}
|
|
if runtimePaths[goPath] {
|
|
continue
|
|
}
|
|
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 runtimeImportAlias(program *Program, goPath, fallback string) string {
|
|
for _, imp := range program.Imports {
|
|
path := strings.Trim(imp.Path, `"`)
|
|
if !strings.Contains(path, "/") {
|
|
path = importPathToGoPath(path)
|
|
}
|
|
if path != goPath {
|
|
continue
|
|
}
|
|
if imp.Alias != "" && imp.Alias != "_" {
|
|
return imp.Alias
|
|
}
|
|
parts := strings.Split(goPath, "/")
|
|
return parts[len(parts)-1]
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
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 NonNullExpr:
|
|
walkExpr(e.Value)
|
|
case TryExpr:
|
|
walkExpr(e.Value)
|
|
case BinaryExpr:
|
|
walkExpr(e.Left)
|
|
walkExpr(e.Right)
|
|
case CallExpr:
|
|
walkExpr(e.Callee)
|
|
for _, arg := range e.Args {
|
|
walkExpr(arg)
|
|
}
|
|
for _, arg := range e.NamedArgs {
|
|
walkExpr(arg.Value)
|
|
}
|
|
case SelectorExpr:
|
|
if alias, ok := selectorRootAlias(e); ok {
|
|
used[alias] = true
|
|
}
|
|
walkExpr(e.Receiver)
|
|
case SafeSelectorExpr:
|
|
walkExpr(e.Receiver)
|
|
case IndexExpr:
|
|
walkExpr(e.Receiver)
|
|
walkExpr(e.Index)
|
|
case EnumVariantExpr:
|
|
for _, value := range e.Values {
|
|
walkExpr(value)
|
|
}
|
|
case MatchExpr:
|
|
walkExpr(e.Value)
|
|
for _, matchCase := range e.Cases {
|
|
walkExpr(matchCase.Value)
|
|
}
|
|
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 DeferStmt:
|
|
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 ForEachStmt:
|
|
walkExpr(s.Source)
|
|
for _, inner := range s.Body {
|
|
walkStmt(inner)
|
|
}
|
|
case MatchStmt:
|
|
walkExpr(s.Value)
|
|
for _, c := range s.Cases {
|
|
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 _, fn := range program.Functions {
|
|
for _, param := range fn.Params {
|
|
markType(param.Type)
|
|
}
|
|
markType(fn.ReturnType)
|
|
for _, stmt := range fn.Body {
|
|
walkStmt(stmt)
|
|
}
|
|
}
|
|
for _, embedded := range program.Embeds {
|
|
markType(embedded.Type)
|
|
}
|
|
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) emitGoUnwrapSupport() {
|
|
g.line("func gotlinUnwrapGo[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) passthroughValue(original Expr, rendered string) string {
|
|
return rendered
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|