Expand Gotlin language and tooling
This commit is contained in:
parent
8f4f858d86
commit
de1262b4cf
41 changed files with 6059 additions and 379 deletions
269
internal/lang/mapping.go
Normal file
269
internal/lang/mapping.go
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
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 {
|
||||
if _, ok := g.classForType(target); ok && !strings.HasPrefix(target, "*") {
|
||||
return "*" + target
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func (g *goGenerator) ensureMapping(source, target, path string) (string, error) {
|
||||
key := source + "->" + target
|
||||
if function, ok := g.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.mappings.pairs)+1)
|
||||
g.mappings.functions[key] = function
|
||||
g.mappings.pairs = append(g.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.enums[strings.TrimPrefix(source, "*")]
|
||||
targetEnum, targetEnumOK := g.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, mapGoType(pair.source), mapGoType(pair.target)))
|
||||
g.indentLevel++
|
||||
if sourceEnum, ok := g.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.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.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, "?")
|
||||
inner, err := g.mappingExpr("*value", sourceInner, targetInner, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.HasSuffix(source, "?") {
|
||||
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; mapped := %s; return &mapped }(%s)", mapGoType(source), mapGoType(target), inner, expr), nil
|
||||
}
|
||||
inner, err = g.mappingExpr("value", sourceInner, targetInner, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("func(value %s) %s { mapped := %s; return &mapped }(%s)", mapGoType(source), mapGoType(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)", mapGoType(source), mapGoType(target), mapGoType(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)", mapGoType(source), mapGoType(target), mapGoType(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)
|
||||
}
|
||||
literal := targetClass.Name + "{" + strings.Join(fields, ", ") + "}"
|
||||
if strings.HasPrefix(target, "*") {
|
||||
literal = "&" + literal
|
||||
}
|
||||
if strings.HasPrefix(source, "*") {
|
||||
return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return %s }(%s)", mapGoType(source), mapGoType(target), strings.ReplaceAll(literal, expr+".", "value."), expr), nil
|
||||
}
|
||||
return literal, nil
|
||||
}
|
||||
if _, ok := g.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.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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue