From fe72ce82234aca671d0992cdc41c77533ba8522a Mon Sep 17 00:00:00 2001 From: Gabriel Nelle Date: Sat, 12 Nov 2022 10:23:08 +0000 Subject: [PATCH] add declaration normalization --- representation/normalize.go | 22 ++-- representation/normalize_decl.go | 168 +++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 representation/normalize_decl.go diff --git a/representation/normalize.go b/representation/normalize.go index 895b818..3bf1797 100644 --- a/representation/normalize.go +++ b/representation/normalize.go @@ -15,17 +15,20 @@ func (s *Representation) Normalize() error { return errors.New("no Go package found") } - pkg = s.normalize(pkg) - s.represent = pkg - return nil -} - -func (s *Representation) normalize(pkg *ast.Package) *ast.Package { f := ast.MergePackageFiles(pkg, ast.FilterImportDuplicates+ast.FilterUnassociatedComments) pkg.Files = map[string]*ast.File{defaultFileName: f} + // register additional normalizations here + s.normalizeDeclarations(pkg) + s.sortInFile(f) + s.normalizeNaming(pkg) + s.represent = pkg + return nil +} + +func (s *Representation) normalizeNaming(pkg *ast.Package) { astutil.Apply(pkg, func(cursor *astutil.Cursor) bool { node := cursor.Node() if node == nil { @@ -33,13 +36,14 @@ func (s *Representation) normalize(pkg *ast.Package) *ast.Package { } s.collectImport(node) - s.rename(node, cursor) + s.rename(cursor) return true }, nil) - return pkg } -func (s *Representation) rename(node ast.Node, cursor *astutil.Cursor) { +func (s *Representation) rename(cursor *astutil.Cursor) { + node := cursor.Node() + switch n := node.(type) { default: return diff --git a/representation/normalize_decl.go b/representation/normalize_decl.go new file mode 100644 index 0000000..0382ffa --- /dev/null +++ b/representation/normalize_decl.go @@ -0,0 +1,168 @@ +package representation + +import ( + "go/ast" + "go/token" + "strconv" + "strings" + + "golang.org/x/tools/go/ast/astutil" +) + +// TODO: test with more cases +func (s *Representation) normalizeDeclarations(pkg *ast.Package) { + astutil.Apply(pkg, func(cursor *astutil.Cursor) bool { + node := cursor.Node() + if node == nil { + return true + } + var ( + a = "abc" + b, c = 1, "cde" + ) + _, _, _ = a, b, c + + switch n := node.(type) { + default: + return true + case *ast.DeclStmt: + genDecl, ok := n.Decl.(*ast.GenDecl) + if !ok { + return true + } + if genDecl.Tok.String() != "var" { + return true + } + + var keep bool + for _, spec := range genDecl.Specs { + specNode, ok := spec.(*ast.ValueSpec) + if !ok { + keep = true + continue + } + + for i, name := range specNode.Names { + val := getValue(specNode.Values, i) + if val == nil { + val = getDefaultLiteralForExpression(specNode.Type) + } + val = addTypeAsNeeded(val, specNode.Type) + newDecl := newAssignment(name.Name, val) + cursor.InsertBefore(newDecl) + } + } + if !keep { + cursor.Delete() + } + } + + return true + }, nil) +} + +func addTypeAsNeeded(val ast.Expr, typeExpr ast.Expr) ast.Expr { + if _, ok := val.(*ast.BasicLit); !ok { + return val + } + typeIdent, ok := typeExpr.(*ast.Ident) + if !ok { + return val + } + if !needsType(typeIdent.Name) { + return val + } + + return &ast.CallExpr{ + Fun: typeIdent, + Args: []ast.Expr{val}, + } +} + +func needsType(ident string) bool { + switch ident { + case "string", "int", "float64", "complex128", "bool": + return false + } + return true +} + +func newAssignment(name string, value ast.Expr) ast.Node { + return &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: name}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{value}, + } +} + +func getValue(values []ast.Expr, i int) ast.Expr { + if len(values) > i { + return values[i] + } + return nil +} + +func getDefaultLiteralForExpression(typeExpr ast.Expr) ast.Expr { + typeIdent, ok := typeExpr.(*ast.Ident) + if !ok { + return &ast.BasicLit{ + Kind: token.STRING, + Value: `"unimplemented case in representer: getDefaultLiteralForExpression: typeExpr is not *ast.Ident"`, + } + } + + kind := typeLookup(typeIdent.Name) + switch kind { + case token.IDENT: + return &ast.CallExpr{ + Fun: typeIdent, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.INT, + Value: `0`, + }, + }, + } + } + return &ast.BasicLit{ + Kind: kind, + Value: getDefaultLiteral(kind), + } +} + +// TODO: test this. Correct string names used? +func typeLookup(name string) token.Token { + switch strings.ToLower(name) { + case "int": + return token.INT + case "float": + return token.FLOAT + case "imag": + return token.IMAG + case "char": + return token.CHAR + case "string": + return token.STRING + } + return token.IDENT +} + +func getDefaultLiteral(kind token.Token) string { + switch kind { + case token.INT: + return `0` + case token.FLOAT: + return `0.0` + case token.IMAG: + return `0i` + case token.CHAR: + return `0` + case token.STRING: + return `""` + } + return `"unimplemented case in representer: getDefaultLiteral: unknown kind: ` + strconv.Itoa(int(kind)) + `"` +} + +// *ast.AssignStmt: &{Lhs:[res] TokPos:110 Tok::= Rhs:[0x140001dc660]} +// *ast.Ident: res +// *ast.BasicLit: &{ValuePos:113 Kind:STRING Value:""}