263 lines
9.5 KiB
Go
263 lines
9.5 KiB
Go
package lang
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type mappingPair struct{ source, target, function string }
|
|
type mappingState struct {
|
|
functions map[string]string
|
|
pairs []mappingPair
|
|
}
|
|
|
|
func (g *goGenerator) mappingTopLevelTarget(target string) string {
|
|
return target
|
|
}
|
|
|
|
func (g *goGenerator) ensureMapping(source, target, path string) (string, error) {
|
|
key := source + "->" + target
|
|
if function, ok := g.semantic.Mappings.functions[key]; ok {
|
|
return function, nil
|
|
}
|
|
if err := g.validateMapping(source, target, path, map[string]bool{}); err != nil {
|
|
return "", err
|
|
}
|
|
function := fmt.Sprintf("gotlinMap%d", len(g.semantic.Mappings.pairs)+1)
|
|
g.semantic.Mappings.functions[key] = function
|
|
g.semantic.Mappings.pairs = append(g.semantic.Mappings.pairs, mappingPair{source: source, target: target, function: function})
|
|
return function, nil
|
|
}
|
|
|
|
func (g *goGenerator) validateMapping(source, target, path string, seen map[string]bool) error {
|
|
if source == target {
|
|
return nil
|
|
}
|
|
key := source + "->" + target
|
|
if seen[key] {
|
|
return nil
|
|
}
|
|
seen[key] = true
|
|
if strings.HasSuffix(source, "?") || strings.HasSuffix(target, "?") {
|
|
if strings.HasSuffix(source, "?") && !strings.HasSuffix(target, "?") {
|
|
return mappingError(path, source, target)
|
|
}
|
|
return g.validateMapping(strings.TrimSuffix(source, "?"), strings.TrimSuffix(target, "?"), path, seen)
|
|
}
|
|
if sourceBase, sourceArgs, ok := parseGenericType(source); ok {
|
|
targetBase, targetArgs, targetOK := parseGenericType(target)
|
|
if !targetOK || sourceBase != targetBase || len(sourceArgs) != len(targetArgs) {
|
|
return mappingError(path, source, target)
|
|
}
|
|
for i := range sourceArgs {
|
|
if sourceBase == "Map" || sourceBase == "MutableMap" {
|
|
if i == 0 && sourceArgs[i] != targetArgs[i] {
|
|
return mappingError(path+".<key>", sourceArgs[i], targetArgs[i])
|
|
}
|
|
}
|
|
if err := g.validateMapping(sourceArgs[i], targetArgs[i], path+"[]", seen); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
sourceClass, sourceClassOK := g.classForType(source)
|
|
targetClass, targetClassOK := g.classForType(target)
|
|
if sourceClassOK || targetClassOK {
|
|
if !sourceClassOK || !targetClassOK {
|
|
return mappingError(path, source, target)
|
|
}
|
|
for _, targetField := range targetClass.Fields {
|
|
sourceField, ok := classFieldByName(sourceClass, targetField.Name)
|
|
fieldPath := path + "." + targetField.Name
|
|
if !ok {
|
|
return fmt.Errorf("cannot map %s: source field is missing for %s.%s", fieldPath, targetClass.Name, targetField.Name)
|
|
}
|
|
if err := g.validateMapping(sourceField.Type, targetField.Type, fieldPath, seen); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
sourceEnum, sourceEnumOK := g.semantic.Enums[strings.TrimPrefix(source, "*")]
|
|
targetEnum, targetEnumOK := g.semantic.Enums[strings.TrimPrefix(target, "*")]
|
|
if sourceEnumOK || targetEnumOK {
|
|
if sourceEnumOK && enumIsString(sourceEnum) && target == "String" {
|
|
return nil
|
|
}
|
|
if targetEnumOK && enumIsString(targetEnum) && source == "String" {
|
|
return nil
|
|
}
|
|
if !sourceEnumOK || !targetEnumOK {
|
|
return mappingError(path, source, target)
|
|
}
|
|
for _, sourceVariant := range sourceEnum.Variants {
|
|
targetVariant := enumVariant(targetEnum, sourceVariant.Name)
|
|
variantPath := path + "." + sourceVariant.Name
|
|
if targetVariant == nil {
|
|
return fmt.Errorf("cannot map %s: target enum %s has no compatible variant", variantPath, targetEnum.Name)
|
|
}
|
|
if len(sourceVariant.PayloadTypes) != len(targetVariant.PayloadTypes) {
|
|
return fmt.Errorf("cannot map %s: payload count %d is incompatible with %d", variantPath, len(sourceVariant.PayloadTypes), len(targetVariant.PayloadTypes))
|
|
}
|
|
for i := range sourceVariant.PayloadTypes {
|
|
if err := g.validateMapping(sourceVariant.PayloadTypes[i], targetVariant.PayloadTypes[i], fmt.Sprintf("%s[%d]", variantPath, i), seen); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return mappingError(path, source, target)
|
|
}
|
|
|
|
func mappingError(path, source, target string) error {
|
|
return fmt.Errorf("cannot map %s: %s is incompatible with %s", path, source, target)
|
|
}
|
|
func classFieldByName(class ClassDecl, name string) (FieldDecl, bool) {
|
|
for _, field := range class.Fields {
|
|
if field.Name == name {
|
|
return field, true
|
|
}
|
|
}
|
|
return FieldDecl{}, false
|
|
}
|
|
func mappingFieldName(class ClassDecl, field FieldDecl) string {
|
|
if class.Data && !field.Private {
|
|
return exportedGoName(field.Name)
|
|
}
|
|
return field.Name
|
|
}
|
|
|
|
func (g *goGenerator) emitMapping(pair mappingPair) error {
|
|
g.line(fmt.Sprintf("func %s(source %s) %s {", pair.function, g.goType(pair.source), g.goType(pair.target)))
|
|
g.indentLevel++
|
|
if sourceEnum, ok := g.semantic.Enums[strings.TrimPrefix(pair.source, "*")]; ok {
|
|
if enumIsString(sourceEnum) && pair.target == "String" {
|
|
g.line("return string(source)")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
return nil
|
|
}
|
|
targetEnum := g.semantic.Enums[strings.TrimPrefix(pair.target, "*")]
|
|
if enumIsString(sourceEnum) && enumIsString(targetEnum) {
|
|
g.line("return " + targetEnum.Name + "(source)")
|
|
g.indentLevel--
|
|
g.line("}")
|
|
return nil
|
|
}
|
|
g.line("switch value := source.(type) {")
|
|
g.indentLevel++
|
|
for _, sourceVariant := range sourceEnum.Variants {
|
|
targetVariant := enumVariant(targetEnum, sourceVariant.Name)
|
|
g.line("case *" + sourceEnum.Name + sourceVariant.Name + ":")
|
|
g.indentLevel++
|
|
fields := make([]string, 0, len(sourceVariant.PayloadTypes))
|
|
for i := range sourceVariant.PayloadTypes {
|
|
expr, err := g.mappingExpr(fmt.Sprintf("value.Value%d", i), sourceVariant.PayloadTypes[i], targetVariant.PayloadTypes[i], sourceEnum.Name+"."+sourceVariant.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fields = append(fields, fmt.Sprintf("Value%d: %s", i, expr))
|
|
}
|
|
g.line("return &" + targetEnum.Name + targetVariant.Name + "{" + strings.Join(fields, ", ") + "}")
|
|
g.indentLevel--
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line(`panic("unreachable enum mapping")`)
|
|
} else if targetEnum, ok := g.semantic.Enums[strings.TrimPrefix(pair.target, "*")]; ok && pair.source == "String" && enumIsString(targetEnum) {
|
|
g.line("switch source {")
|
|
g.indentLevel++
|
|
for _, variant := range targetEnum.Variants {
|
|
g.line("case " + strconv.Quote(enumStringValue(variant)) + ":")
|
|
g.indentLevel++
|
|
g.line("return " + targetEnum.Name + variant.Name)
|
|
g.indentLevel--
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
g.line(`panic("unknown enum string: " + source)`)
|
|
} else {
|
|
expr, err := g.mappingExpr("source", pair.source, pair.target, strings.TrimPrefix(pair.source, "*"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.line("return " + expr)
|
|
}
|
|
g.indentLevel--
|
|
g.line("}")
|
|
return nil
|
|
}
|
|
|
|
func (g *goGenerator) mappingExpr(expr, source, target, path string) (string, error) {
|
|
if source == target {
|
|
return expr, nil
|
|
}
|
|
if strings.HasSuffix(source, "?") || strings.HasSuffix(target, "?") {
|
|
sourceInner := strings.TrimSuffix(source, "?")
|
|
targetInner := strings.TrimSuffix(target, "?")
|
|
innerExpr := "*value"
|
|
if _, class := g.classForType(sourceInner); class {
|
|
innerExpr = "value"
|
|
}
|
|
inner, err := g.mappingExpr(innerExpr, sourceInner, targetInner, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if strings.HasSuffix(source, "?") {
|
|
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return %s }(%s)", g.goType(source), g.goType(target), inner, expr), nil
|
|
}
|
|
inner, err = g.mappingExpr("value", sourceInner, targetInner, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("func(value %s) %s { return %s }(%s)", g.goType(source), g.goType(target), inner, expr), nil
|
|
}
|
|
if sourceBase, sourceArgs, ok := parseGenericType(source); ok {
|
|
_, targetArgs, _ := parseGenericType(target)
|
|
if sourceBase == "List" || sourceBase == "MutableList" {
|
|
item, err := g.mappingExpr("item", sourceArgs[0], targetArgs[0], path+"[]")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("func(values %s) %s { var result %s; for _, item := range values { result = append(result, %s) }; return result }(%s)", g.goType(source), g.goType(target), g.goType(target), item, expr), nil
|
|
}
|
|
if sourceBase == "Map" || sourceBase == "MutableMap" {
|
|
value, err := g.mappingExpr("item", sourceArgs[1], targetArgs[1], path+"[]")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("func(values %s) %s { result := make(%s, len(values)); for key, item := range values { result[key] = %s }; return result }(%s)", g.goType(source), g.goType(target), g.goType(target), value, expr), nil
|
|
}
|
|
}
|
|
if sourceClass, ok := g.classForType(source); ok {
|
|
targetClass, _ := g.classForType(target)
|
|
fields := make([]string, 0, len(targetClass.Fields))
|
|
for _, targetField := range targetClass.Fields {
|
|
sourceField, _ := classFieldByName(sourceClass, targetField.Name)
|
|
mapped, err := g.mappingExpr(expr+"."+mappingFieldName(sourceClass, sourceField), sourceField.Type, targetField.Type, path+"."+targetField.Name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fields = append(fields, mappingFieldName(targetClass, targetField)+": "+mapped)
|
|
}
|
|
return "&" + targetClass.Name + "{" + strings.Join(fields, ", ") + "}", nil
|
|
}
|
|
if _, ok := g.semantic.Enums[strings.TrimPrefix(source, "*")]; ok {
|
|
function, err := g.ensureMapping(source, target, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return function + "(" + expr + ")", nil
|
|
}
|
|
if targetEnum, ok := g.semantic.Enums[strings.TrimPrefix(target, "*")]; ok && source == "String" && enumIsString(targetEnum) {
|
|
function, err := g.ensureMapping(source, target, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return function + "(" + expr + ")", nil
|
|
}
|
|
return "", mappingError(path, source, target)
|
|
}
|