├── .gitignore ├── Makefile ├── README.md ├── astutil └── type_parsing.go ├── codegen_test.go ├── declaration.go ├── dependency_tree_test.go ├── dot └── file.go ├── expression.go ├── generate.go ├── go.mod ├── go.sum ├── java2go.go ├── nodeutil ├── assertions.go └── node_helpers.go ├── parsing ├── parsing_tools.go └── source_file.go ├── resolve.go ├── statement.go ├── stdjava ├── README.md ├── common.go ├── hashcode_test.go ├── multidim_test.go ├── optional.go └── shift_test.go ├── symbol ├── class_scope.go ├── definition.go ├── file_scope.go ├── find.go ├── globals.go ├── package_scope.go ├── parsing.go ├── parsing_helpers.go └── symbols.go ├── testfiles ├── AnnotatedPet.java ├── ClassWithFieldNamedType.java ├── Compass.java ├── ConditionOrdering.java ├── EnhancedForLoop.java ├── GenericLinkedList.java ├── IncrementDecrement.java ├── InlineStatements.java ├── IntLinkedList.java ├── LinkedList.java ├── LiteralsAndNulls.java ├── MathUtilsStatic.java ├── MultiDimensionalArrays.java ├── NameCollisions.java ├── RandomTypes.java ├── ResolveTesting.java ├── ScrambledForLoops.java ├── SelectorNewExpression.java ├── SimpleTest.java ├── Test.java ├── TestInterface.java ├── VariableAssignments.java ├── extendsandimplements.java ├── output │ ├── IntLinkedList.go │ ├── Test.go │ ├── class-with-type-field.go │ └── simple.go └── typechecks │ ├── MethodConstructorDeclaration.java │ ├── SimpleDeclaration.java │ └── UntypedMap.java ├── tree_sitter.go └── typecheck_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Main binary 2 | java2go 3 | 4 | # Intemediary decompiled directories 5 | quiltflower-src/ 6 | intellij-fernflower/ 7 | yarn/ 8 | 9 | # Decompiled code 10 | fernflower/ 11 | quiltflower/ 12 | namedSrc/ 13 | *.jar 14 | 15 | # Analytic files 16 | *.dot 17 | 18 | # Compiled test files 19 | *.class 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Helper script for decompiling some files for testing 2 | 3 | fernflower = fabric-fernflower-1.4.1+local.jar 4 | quiltflower = quiltflower-1.8.1+local.jar 5 | namedjar = 1.16.5-named.jar 6 | procyon = `curl --silent https://api.github.com/repos/mstrobel/procyon/releases/latest | jq -r .assets[0].name` 7 | 8 | decompile: quiltflower 9 | go run . --output=out --exclude-annotations="@Environment(EnvType.CLIENT)" -w quiltflower/ 10 | 11 | clean: 12 | # Build files 13 | -rm -rf quiltflower-src 14 | -rm -rf yarn 15 | -rm -rf intellij-fernflower 16 | -rm ${procyon}* 17 | # Decompiled files 18 | -rm -r yarn fernflower quiltflower namedSrc procyon 19 | 20 | # Yarn generates the deobfuscated jar files to decompile 21 | yarn: 22 | git clone -b 1.16.5 git@github.com:FabricMC/yarn.git 23 | #git clone -b 1.16.5 git@github.com:QuiltMC/yarn.git 24 | cd yarn && ./gradlew --no-daemon mapNamedJar 25 | 26 | # Uses the quiltflower decompiler for decompilation 27 | quiltflower: yarn 28 | git clone git@github.com:QuiltMC/quiltflower.git quiltflower-src 29 | cd quiltflower-src && ./gradlew --no-daemon build 30 | mkdir quiltflower 31 | java -jar "quiltflower-src/build/libs/${quiltflower}" -rsy=1 "yarn/${namedjar}" quiltflower 32 | 33 | # Uses the CFR decompiler for decompilation 34 | CFR: yarn 35 | cd yarn && ./gradlew --no-daemon decompileCFR 36 | mv yarn/namedSrc . 37 | 38 | # Uses the Procyon decompiler for decompilation 39 | procyon: yarn 40 | wget `curl --silent "https://api.github.com/repos/mstrobel/procyon/releases/latest" | jq -r .assets[0].browser_download_url` 41 | java -jar "${procyon}" -jar "yarn/${namedjar}" -o procyon 42 | 43 | # Uses the fernflower decompiler for decompilation 44 | fernflower: yarn 45 | git clone git@github.com:FabricMC/intellij-fernflower.git 46 | cd intellij-fernflower && ./gradlew --no-daemon build 47 | mkdir fernflower 48 | java -jar "intellij-fernflower/build/libs/${fernflower}" -rsy=1 "yarn/${namedjar}" fernflower 49 | unzip "fernflower/${namedjar}" -d fernflower 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java2go 2 | ## About 3 | 4 | Java2go is a transpiler that automatically converts Java source code to compatible Go code 5 | 6 | It does this through several steps: 7 | 8 | 1. Parse the java source code into a [`tree-sitter`](https://github.com/smacker/go-tree-sitter) AST 9 | 10 | 2. Convert that AST into Golang's own internal [AST representation](https://pkg.go.dev/go/ast) 11 | 12 | 3. Use Golang's builtin [AST printer](https://pkg.go.dev/go/printer) to print out the generated code 13 | 14 | ## Issues 15 | 16 | Note: Java2go is still in development, and as such, please expect many bugs 17 | 18 | Currently, the following features are not implemented 19 | 20 | * [ ] Enum classes (Fully) 21 | * [ ] Generic types 22 | * [ ] Any type of inheritance 23 | * [ ] Abstract classes 24 | * [ ] Lambda interfaces 25 | * [ ] Inheritance 26 | * [ ] Decorators 27 | * [ ] Anything that checks `instanceof` 28 | * [ ] Types for lambda expressions 29 | 30 | ## Usage 31 | 32 | * Clone the repo 33 | 34 | * `go build` to build the java2go binary 35 | 36 | * `./java2go ` to parse a list of files or directories 37 | 38 | ## Options 39 | 40 | * `-w` writes the files directly to their corresponding `.go` files, instead of `stdout` 41 | 42 | * `-output` specifies an alternate directory for the generated files. Defaults to putting them next to their source files by default 43 | 44 | * `-q` prevents the outputs of the parsed files from appearing on `stdout`, if not being written 45 | 46 | * `-ast` pretty-prints the generated ast, in addition to any other options 47 | 48 | * `-symbols` (WIP) controls whether the parser uses internal symbol tables to handle things such as name collistions, resulting in better code generation at the cost of increased parser complexity (default: true) 49 | 50 | * `-sync` parses the files in sequential order, instead of in parallel 51 | 52 | * `-exclude-annotations` specifies a list of annotations on methods and fields that will exclude them from the generated code 53 | -------------------------------------------------------------------------------- /astutil/type_parsing.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | 7 | sitter "github.com/smacker/go-tree-sitter" 8 | ) 9 | 10 | func ParseType(node *sitter.Node, source []byte) ast.Expr { 11 | switch node.Type() { 12 | case "integral_type": 13 | switch node.Child(0).Type() { 14 | case "int": 15 | return &ast.Ident{Name: "int32"} 16 | case "short": 17 | return &ast.Ident{Name: "int16"} 18 | case "long": 19 | return &ast.Ident{Name: "int64"} 20 | case "char": 21 | return &ast.Ident{Name: "rune"} 22 | case "byte": 23 | return &ast.Ident{Name: node.Content(source)} 24 | } 25 | 26 | panic(fmt.Errorf("Unknown integral type: %v", node.Child(0).Type())) 27 | case "floating_point_type": // Can be either `float` or `double` 28 | switch node.Child(0).Type() { 29 | case "float": 30 | return &ast.Ident{Name: "float32"} 31 | case "double": 32 | return &ast.Ident{Name: "float64"} 33 | } 34 | 35 | panic(fmt.Errorf("Unknown float type: %v", node.Child(0).Type())) 36 | case "void_type": 37 | return &ast.Ident{} 38 | case "boolean_type": 39 | return &ast.Ident{Name: "bool"} 40 | case "generic_type": 41 | // A generic type is any type that is of the form GenericType 42 | return &ast.Ident{Name: node.NamedChild(0).Content(source)} 43 | case "array_type": 44 | return &ast.ArrayType{Elt: ParseType(node.NamedChild(0), source)} 45 | case "type_identifier": // Any reference type 46 | switch node.Content(source) { 47 | // Special case for strings, because in Go, these are primitive types 48 | case "String": 49 | return &ast.Ident{Name: "string"} 50 | } 51 | 52 | return &ast.StarExpr{ 53 | X: &ast.Ident{Name: node.Content(source)}, 54 | } 55 | case "scoped_type_identifier": 56 | // This contains a reference to the type of a nested class 57 | // Ex: LinkedList.Node 58 | return &ast.StarExpr{X: &ast.Ident{Name: node.Content(source)}} 59 | } 60 | panic("Unknown type to convert: " + node.Type()) 61 | } 62 | -------------------------------------------------------------------------------- /codegen_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "go/ast" 7 | "go/printer" 8 | "go/token" 9 | "os" 10 | "testing" 11 | 12 | sitter "github.com/smacker/go-tree-sitter" 13 | "github.com/smacker/go-tree-sitter/java" 14 | ) 15 | 16 | // ParseSourceAst parses a given source file and returns the tree-sitter root 17 | // node for the AST associated with that file 18 | func ParseSourceAst(fileName string) (*sitter.Node, []byte) { 19 | parser := sitter.NewParser() 20 | parser.SetLanguage(java.GetLanguage()) 21 | 22 | sourceCode, err := os.ReadFile(fileName) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | tree, err := parser.ParseCtx(context.Background(), nil, sourceCode) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | return tree.RootNode(), sourceCode 33 | } 34 | 35 | func ParseAst(fileName string) ast.Node { 36 | root, source := ParseSourceAst(fileName) 37 | return ParseNode(root, source, Ctx{}).(ast.Node) 38 | } 39 | 40 | // This tests the increment and decrement handling on increment and decrement 41 | // statements, as well as expressions 42 | func TestIncDec(t *testing.T) { 43 | var generated bytes.Buffer 44 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/IncrementDecrement.java")) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | t.Log(generated.String()) 49 | } 50 | 51 | // This tests the variable assignment handling, using it as a statement as 52 | // well as an expression 53 | func TestAssignments(t *testing.T) { 54 | var generated bytes.Buffer 55 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/VariableAssignments.java")) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | t.Log(generated.String()) 60 | } 61 | 62 | // This tests two alternate ways of calling the new constructor 63 | func TestAlternateNewCall(t *testing.T) { 64 | var generated bytes.Buffer 65 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/SelectorNewExpression.java")) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | t.Log(generated.String()) 70 | } 71 | 72 | // This tests for various combinations of init, cond, and post parts of for loops 73 | func TestScrambledForLoops(t *testing.T) { 74 | var generated bytes.Buffer 75 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/ScrambledForLoops.java")) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | t.Log(generated.String()) 80 | } 81 | 82 | // This tests for the correct handling of generics (Implemented with go 1.18) 83 | func TestGenericLinkedlist(t *testing.T) { 84 | var generated bytes.Buffer 85 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/GenericLinkedList.java")) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | t.Log(generated.String()) 90 | } 91 | 92 | func TestConditionOrdering(t *testing.T) { 93 | var generated bytes.Buffer 94 | err := printer.Fprint(&generated, token.NewFileSet(), ParseAst("testfiles/ConditionOrdering.java")) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | t.Log(generated.String()) 99 | } 100 | -------------------------------------------------------------------------------- /declaration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | 7 | "github.com/NickyBoy89/java2go/nodeutil" 8 | "github.com/NickyBoy89/java2go/symbol" 9 | log "github.com/sirupsen/logrus" 10 | sitter "github.com/smacker/go-tree-sitter" 11 | ) 12 | 13 | // ParseDecls represents any type that returns a list of top-level declarations, 14 | // this is any class, interface, or enum declaration 15 | func ParseDecls(node *sitter.Node, source []byte, ctx Ctx) []ast.Decl { 16 | switch node.Type() { 17 | case "class_declaration": 18 | // TODO: Currently ignores implements and extends with the following tags: 19 | //"superclass" 20 | //"interfaces" 21 | 22 | // The declarations and fields for the class 23 | declarations := []ast.Decl{} 24 | fields := &ast.FieldList{} 25 | 26 | // Global variables 27 | globalVariables := &ast.GenDecl{Tok: token.VAR} 28 | 29 | ctx.className = ctx.currentFile.FindClass(node.ChildByFieldName("name").Content(source)).Name 30 | 31 | // First, look through the class's body for field declarations 32 | for _, child := range nodeutil.NamedChildrenOf(node.ChildByFieldName("body")) { 33 | if child.Type() == "field_declaration" { 34 | 35 | var staticField bool 36 | 37 | comments := []*ast.Comment{} 38 | 39 | // Handle any modifiers that the field might have 40 | if child.NamedChild(0).Type() == "modifiers" { 41 | for _, modifier := range nodeutil.UnnamedChildrenOf(child.NamedChild(0)) { 42 | switch modifier.Type() { 43 | case "static": 44 | staticField = true 45 | case "marker_annotation", "annotation": 46 | comments = append(comments, &ast.Comment{Text: "//" + modifier.Content(source)}) 47 | if _, in := excludedAnnotations[modifier.Content(source)]; in { 48 | // Skip this field if there is an ignored annotation 49 | continue 50 | } 51 | } 52 | } 53 | } 54 | 55 | // TODO: If a field is initialized to a value, that value is discarded 56 | 57 | field := &ast.Field{} 58 | if len(comments) > 0 { 59 | field.Doc = &ast.CommentGroup{List: comments} 60 | } 61 | 62 | fieldName := child.ChildByFieldName("declarator").ChildByFieldName("name").Content(source) 63 | 64 | fieldDef := ctx.currentClass.FindField().ByOriginalName(fieldName)[0] 65 | 66 | field.Names, field.Type = []*ast.Ident{&ast.Ident{Name: fieldDef.Name}}, &ast.Ident{Name: fieldDef.Type} 67 | 68 | if staticField { 69 | globalVariables.Specs = append(globalVariables.Specs, &ast.ValueSpec{Names: field.Names, Type: field.Type}) 70 | } else { 71 | fields.List = append(fields.List, field) 72 | } 73 | } 74 | } 75 | 76 | // Add the global variables 77 | if len(globalVariables.Specs) > 0 { 78 | declarations = append(declarations, globalVariables) 79 | } 80 | 81 | // Add any type paramters defined in the class 82 | if node.ChildByFieldName("type_parameters") != nil { 83 | declarations = append(declarations, ParseDecls(node.ChildByFieldName("type_parameters"), source, ctx)...) 84 | } 85 | 86 | // Add the struct for the class 87 | declarations = append(declarations, GenStruct(ctx.className, fields)) 88 | 89 | // Add all the declarations that appear in the class 90 | declarations = append(declarations, ParseDecls(node.ChildByFieldName("body"), source, ctx)...) 91 | 92 | return declarations 93 | case "class_body": // The body of the currently parsed class 94 | decls := []ast.Decl{} 95 | 96 | // To switch to parsing the subclasses of a class, since we assume that 97 | // all the class's subclass definitions are in-order, if we find some number 98 | // of subclasses in a class, we can refer to them by index 99 | var subclassIndex int 100 | 101 | for _, child := range nodeutil.NamedChildrenOf(node) { 102 | switch child.Type() { 103 | // Skip fields and comments 104 | case "field_declaration", "comment": 105 | case "constructor_declaration", "method_declaration", "static_initializer": 106 | d := ParseDecl(child, source, ctx) 107 | // If the declaration is bad, skip it 108 | _, bad := d.(*ast.BadDecl) 109 | if !bad { 110 | decls = append(decls, d) 111 | } 112 | 113 | // Subclasses 114 | case "class_declaration", "interface_declaration", "enum_declaration": 115 | newCtx := ctx.Clone() 116 | newCtx.currentClass = ctx.currentClass.Subclasses[subclassIndex] 117 | subclassIndex++ 118 | decls = append(decls, ParseDecls(child, source, newCtx)...) 119 | } 120 | } 121 | 122 | return decls 123 | case "interface_body": 124 | methods := &ast.FieldList{} 125 | 126 | for _, c := range nodeutil.NamedChildrenOf(node) { 127 | if c.Type() == "method_declaration" { 128 | parsedMethod := ParseNode(c, source, ctx).(*ast.Field) 129 | // If the method was ignored with an annotation, it will return a blank 130 | // field, so ignore that 131 | if parsedMethod.Type != nil { 132 | methods.List = append(methods.List, parsedMethod) 133 | } 134 | } 135 | } 136 | 137 | return []ast.Decl{GenInterface(ctx.className, methods)} 138 | case "interface_declaration": 139 | ctx.className = ctx.currentFile.FindClass(node.ChildByFieldName("name").Content(source)).Name 140 | 141 | return ParseDecls(node.ChildByFieldName("body"), source, ctx) 142 | case "enum_declaration": 143 | // An enum is treated as both a struct, and a list of values that define 144 | // the states that the enum can be in 145 | 146 | ctx.className = ctx.currentFile.FindClass(node.ChildByFieldName("name").Content(source)).Name 147 | 148 | // TODO: Handle an enum correctly 149 | //return ParseDecls(node.ChildByFieldName("body"), source, ctx) 150 | return []ast.Decl{} 151 | case "type_parameters": 152 | var declarations []ast.Decl 153 | 154 | // A list of generic type parameters 155 | for _, param := range nodeutil.NamedChildrenOf(node) { 156 | switch param.Type() { 157 | case "type_parameter": 158 | declarations = append(declarations, GenTypeInterface(param.NamedChild(0).Content(source), []string{"any"})) 159 | } 160 | } 161 | 162 | return declarations 163 | } 164 | panic("Unknown type to parse for decls: " + node.Type()) 165 | } 166 | 167 | // ParseDecl parses a top-level declaration within a source file, including 168 | // but not limited to fields and methods 169 | func ParseDecl(node *sitter.Node, source []byte, ctx Ctx) ast.Decl { 170 | switch node.Type() { 171 | case "constructor_declaration": 172 | paramNode := node.ChildByFieldName("parameters") 173 | 174 | constructorName := node.ChildByFieldName("name").Content(source) 175 | 176 | comparison := func(d *symbol.Definition) bool { 177 | // The names must match 178 | if constructorName != d.OriginalName { 179 | return false 180 | } 181 | 182 | // Size of parameters must match 183 | if int(paramNode.NamedChildCount()) != len(d.Parameters) { 184 | return false 185 | } 186 | 187 | // Go through the types and check to see if they differ 188 | for index, param := range nodeutil.NamedChildrenOf(paramNode) { 189 | var paramType string 190 | if param.Type() == "spread_parameter" { 191 | paramType = param.NamedChild(0).Content(source) 192 | } else { 193 | paramType = param.ChildByFieldName("type").Content(source) 194 | } 195 | if paramType != d.Parameters[index].OriginalType { 196 | return false 197 | } 198 | } 199 | 200 | return true 201 | } 202 | 203 | // Search through the current class for the constructor, which is simply labeled as a method 204 | ctx.localScope = ctx.currentClass.FindMethod().By(comparison)[0] 205 | 206 | body := ParseStmt(node.ChildByFieldName("body"), source, ctx).(*ast.BlockStmt) 207 | 208 | body.List = append([]ast.Stmt{ 209 | &ast.AssignStmt{ 210 | Lhs: []ast.Expr{&ast.Ident{Name: ShortName(ctx.className)}}, 211 | Tok: token.DEFINE, 212 | Rhs: []ast.Expr{&ast.CallExpr{Fun: &ast.Ident{Name: "new"}, Args: []ast.Expr{&ast.Ident{Name: ctx.className}}}}, 213 | }, 214 | }, body.List...) 215 | 216 | body.List = append(body.List, &ast.ReturnStmt{Results: []ast.Expr{&ast.Ident{Name: ShortName(ctx.className)}}}) 217 | 218 | return &ast.FuncDecl{ 219 | Name: &ast.Ident{Name: ctx.localScope.Name}, 220 | Type: &ast.FuncType{ 221 | Params: ParseNode(node.ChildByFieldName("parameters"), source, ctx).(*ast.FieldList), 222 | Results: &ast.FieldList{List: []*ast.Field{&ast.Field{ 223 | Type: &ast.Ident{Name: ctx.localScope.Type}, 224 | }}}, 225 | }, 226 | Body: body, 227 | } 228 | case "method_declaration": 229 | var static bool 230 | 231 | // Store the annotations as comments on the method 232 | comments := []*ast.Comment{} 233 | 234 | if node.NamedChild(0).Type() == "modifiers" { 235 | for _, modifier := range nodeutil.UnnamedChildrenOf(node.NamedChild(0)) { 236 | switch modifier.Type() { 237 | case "static": 238 | static = true 239 | case "abstract": 240 | log.Warn("Unhandled abstract class") 241 | // TODO: Handle abstract methods correctly 242 | return &ast.BadDecl{} 243 | case "marker_annotation", "annotation": 244 | comments = append(comments, &ast.Comment{Text: "//" + modifier.Content(source)}) 245 | // If the annotation was on the list of ignored annotations, don't 246 | // parse the method 247 | if _, in := excludedAnnotations[modifier.Content(source)]; in { 248 | return &ast.BadDecl{} 249 | } 250 | } 251 | } 252 | } 253 | 254 | var receiver *ast.FieldList 255 | 256 | // If a function is non-static, it has a method receiver 257 | if !static { 258 | receiver = &ast.FieldList{ 259 | List: []*ast.Field{ 260 | &ast.Field{ 261 | Names: []*ast.Ident{&ast.Ident{Name: ShortName(ctx.className)}}, 262 | Type: &ast.StarExpr{X: &ast.Ident{Name: ctx.className}}, 263 | }, 264 | }, 265 | } 266 | } 267 | 268 | methodName := ParseExpr(node.ChildByFieldName("name"), source, ctx).(*ast.Ident) 269 | 270 | methodParameters := node.ChildByFieldName("parameters") 271 | 272 | // Find the declaration for the method that we are defining 273 | 274 | // Find a method that is more or less exactly the same 275 | comparison := func(d *symbol.Definition) bool { 276 | // Throw out any methods that aren't named the same 277 | if d.OriginalName != methodName.Name { 278 | return false 279 | } 280 | 281 | // Now, even though the method might have the same name, it could be overloaded, 282 | // so we have to check the parameters as well 283 | 284 | // Number of parameters are not the same, invalid 285 | if len(d.Parameters) != int(methodParameters.NamedChildCount()) { 286 | return false 287 | } 288 | 289 | // Go through the types and check to see if they differ 290 | for index, param := range nodeutil.NamedChildrenOf(methodParameters) { 291 | var paramType string 292 | if param.Type() == "spread_parameter" { 293 | paramType = param.NamedChild(0).Content(source) 294 | } else { 295 | paramType = param.ChildByFieldName("type").Content(source) 296 | } 297 | if d.Parameters[index].OriginalType != paramType { 298 | return false 299 | } 300 | } 301 | 302 | // We found the correct method 303 | return true 304 | } 305 | 306 | methodDefinition := ctx.currentClass.FindMethod().By(comparison) 307 | 308 | // No definition was found 309 | if len(methodDefinition) == 0 { 310 | log.WithFields(log.Fields{ 311 | "methodName": methodName.Name, 312 | }).Panic("No matching definition found for method") 313 | } 314 | 315 | ctx.localScope = methodDefinition[0] 316 | 317 | body := ParseStmt(node.ChildByFieldName("body"), source, ctx).(*ast.BlockStmt) 318 | 319 | params := ParseNode(node.ChildByFieldName("parameters"), source, ctx).(*ast.FieldList) 320 | 321 | // Special case for the main method, because in Java, this method has the 322 | // command line args passed in as a parameter 323 | if methodName.Name == "main" { 324 | params = nil 325 | body.List = append([]ast.Stmt{ 326 | &ast.AssignStmt{ 327 | Lhs: []ast.Expr{&ast.Ident{Name: "args"}}, 328 | Tok: token.DEFINE, 329 | Rhs: []ast.Expr{ 330 | &ast.SelectorExpr{ 331 | X: &ast.Ident{Name: "os"}, 332 | Sel: &ast.Ident{Name: "Args"}, 333 | }, 334 | }, 335 | }, 336 | }, body.List...) 337 | } 338 | 339 | return &ast.FuncDecl{ 340 | Doc: &ast.CommentGroup{List: comments}, 341 | Name: &ast.Ident{Name: ctx.localScope.Name}, 342 | Recv: receiver, 343 | Type: &ast.FuncType{ 344 | Params: params, 345 | Results: &ast.FieldList{ 346 | List: []*ast.Field{ 347 | &ast.Field{Type: &ast.Ident{Name: ctx.localScope.Type}}, 348 | }, 349 | }, 350 | }, 351 | Body: body, 352 | } 353 | case "static_initializer": 354 | 355 | ctx.localScope = &symbol.Definition{} 356 | 357 | // A block of `static`, which is run before the main function 358 | return &ast.FuncDecl{ 359 | Name: &ast.Ident{Name: "init"}, 360 | Type: &ast.FuncType{ 361 | Params: &ast.FieldList{List: []*ast.Field{}}, 362 | }, 363 | Body: ParseStmt(node.NamedChild(0), source, ctx).(*ast.BlockStmt), 364 | } 365 | } 366 | 367 | panic("Unknown node type for declaration: " + node.Type()) 368 | } 369 | -------------------------------------------------------------------------------- /dependency_tree_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /dot/file.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type Dotfile struct { 10 | SubGraph 11 | *os.File 12 | } 13 | 14 | func (d Dotfile) Name() string { 15 | return d.SubGraph.Name() 16 | } 17 | 18 | func (d *Dotfile) DeleteSubgraph(name string) { 19 | delete(d.SubGraph.subgraphs, name) 20 | } 21 | 22 | type GraphPrinter interface { 23 | AsDot() (string, []Edge) 24 | Name() string 25 | } 26 | 27 | // GraphItem is any item in the graph 28 | type GraphItem interface { 29 | GraphPrinter 30 | HasSubgraph(name string) bool // Whether the item has the given subgraph 31 | Subgraph(name string) GraphItem // Returns the named subgraph, adding it if necessary 32 | AddNode(name string, edges ...string) // Adds a node to the graph 33 | } 34 | 35 | type Edge struct { 36 | From string 37 | To []string 38 | } 39 | 40 | type SubGraph struct { 41 | name string 42 | nodes map[string]Node 43 | subgraphs map[string]GraphItem 44 | } 45 | 46 | // Methods for GraphItem 47 | func (g SubGraph) HasSubgraph(name string) bool { 48 | _, has := g.subgraphs[name] 49 | return has 50 | } 51 | 52 | func (g *SubGraph) Subgraph(name string) GraphItem { 53 | if g.subgraphs == nil { 54 | g.subgraphs = make(map[string]GraphItem) 55 | } 56 | if _, in := g.subgraphs[name]; !in { 57 | g.subgraphs[name] = &SubGraph{name: name} 58 | } 59 | return g.subgraphs[name] 60 | } 61 | 62 | func (g *SubGraph) AddNode(name string, edges ...string) { 63 | if g.nodes == nil { 64 | g.nodes = make(map[string]Node) 65 | } 66 | g.nodes[name] = Node{name: name, edges: edges} 67 | } 68 | 69 | func (g SubGraph) Name() string { 70 | return g.name 71 | } 72 | 73 | func (g SubGraph) AsDot() (string, []Edge) { 74 | totalEdges := []Edge{} 75 | total := fmt.Sprintf("subgraph cluster_%s {\n", g.name) 76 | total += fmt.Sprintf("label=\"%s\"\n", g.name) 77 | for _, item := range g.nodes { 78 | totalEdges = append(totalEdges, Edge{From: item.name, To: item.edges}) 79 | total += item.Name() + "\n" 80 | } 81 | for _, item := range g.subgraphs { 82 | sub, edges := item.AsDot() 83 | total += sub + "\n" 84 | totalEdges = append(totalEdges, edges...) 85 | } 86 | return total + "}", totalEdges 87 | } 88 | 89 | type Node struct { 90 | name string 91 | edges []string 92 | } 93 | 94 | func (n Node) AsDot() (string, []Edge) { 95 | return "", []Edge{Edge{From: n.name, To: n.edges}} 96 | } 97 | 98 | func (n Node) Name() string { 99 | return n.name 100 | } 101 | 102 | func New(name string) (*Dotfile, error) { 103 | file, err := os.Create(name) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return &Dotfile{File: file}, nil 108 | } 109 | 110 | func (d *Dotfile) AddNode(name string, edges ...string) { 111 | if d.nodes == nil { 112 | d.nodes = make(map[string]Node) 113 | } 114 | d.nodes[name] = Node{name: name, edges: edges} 115 | } 116 | 117 | func (d *Dotfile) HasNode(name string) bool { 118 | _, in := d.nodes[name] 119 | return in 120 | } 121 | 122 | func (d *Dotfile) AddEdge(node string, edge string) { 123 | temp := d.nodes[node] 124 | // If the node doesn't exist, create it 125 | if temp.name == "" { 126 | temp = Node{name: node} 127 | } 128 | temp.edges = append(temp.edges, edge) 129 | d.nodes[node] = temp 130 | } 131 | 132 | func (d *Dotfile) HasEdge(node string, edge string) bool { 133 | for _, e := range d.nodes[node].edges { 134 | if e == edge { 135 | return true 136 | } 137 | } 138 | return false 139 | } 140 | 141 | func commaSeparatedString(list []string) string { 142 | var total strings.Builder 143 | for ind, item := range list { 144 | total.WriteString("\"" + item + "\"") 145 | if ind < len(list)-1 { 146 | total.WriteString(", ") 147 | } 148 | } 149 | return total.String() 150 | } 151 | 152 | func (d *Dotfile) WriteToFile() { 153 | totalEdges := []Edge{} 154 | d.WriteString("digraph {\n") 155 | //d.WriteString("compound=true\n") 156 | 157 | // First, write out all the subgraphs 158 | for _, graph := range d.subgraphs { 159 | sub, edges := graph.AsDot() 160 | totalEdges = append(totalEdges, edges...) 161 | d.WriteString(sub + "\n") 162 | } 163 | 164 | // Then, go through the nodes 165 | for name, node := range d.nodes { 166 | fmt.Fprintf(d, " \"%s\" -> {%s}\n", name, commaSeparatedString(node.edges)) 167 | } 168 | 169 | // Finally, connect all the edges from everything else 170 | for _, edge := range totalEdges { 171 | // Skip creating edges that don't point anywhere 172 | if len(edge.To) == 0 { 173 | continue 174 | } 175 | // Also skip empty nodes 176 | if edge.From == "" { 177 | continue 178 | } 179 | fmt.Fprintf(d, "\"%s\" -> {%s}\n", edge.From, commaSeparatedString(edge.To)) 180 | } 181 | d.WriteString("}") 182 | } 183 | -------------------------------------------------------------------------------- /expression.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | 8 | "github.com/NickyBoy89/java2go/astutil" 9 | "github.com/NickyBoy89/java2go/nodeutil" 10 | "github.com/NickyBoy89/java2go/symbol" 11 | log "github.com/sirupsen/logrus" 12 | sitter "github.com/smacker/go-tree-sitter" 13 | ) 14 | 15 | // ParseExpr parses an expression type 16 | func ParseExpr(node *sitter.Node, source []byte, ctx Ctx) ast.Expr { 17 | switch node.Type() { 18 | case "ERROR": 19 | log.WithFields(log.Fields{ 20 | "parsed": node.Content(source), 21 | "className": ctx.className, 22 | }).Warn("Expression parse error") 23 | return &ast.BadExpr{} 24 | case "comment": 25 | return &ast.BadExpr{} 26 | case "update_expression": 27 | // This can either be a pre or post expression 28 | // a pre expression has the identifier second, while the post expression 29 | // has the identifier first 30 | 31 | // Post-update expression, e.g. `i++` 32 | if node.Child(0).IsNamed() { 33 | return &ast.CallExpr{ 34 | Fun: &ast.Ident{Name: "PostUpdate"}, 35 | Args: []ast.Expr{ParseExpr(node.Child(0), source, ctx)}, 36 | } 37 | } 38 | 39 | // Otherwise, pre-update expression 40 | return &ast.CallExpr{ 41 | Fun: &ast.Ident{Name: "PreUpdate"}, 42 | Args: []ast.Expr{ParseExpr(node.Child(1), source, ctx)}, 43 | } 44 | case "class_literal": 45 | // Class literals refer to the class directly, such as 46 | // Object.class 47 | return &ast.BadExpr{} 48 | case "assignment_expression": 49 | return &ast.CallExpr{ 50 | Fun: &ast.Ident{Name: "AssignmentExpression"}, 51 | Args: []ast.Expr{ 52 | ParseExpr(node.Child(0), source, ctx), 53 | &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", node.Child(1).Content(source))}, 54 | ParseExpr(node.Child(2), source, ctx), 55 | }, 56 | } 57 | case "super": 58 | return &ast.BadExpr{} 59 | case "lambda_expression": 60 | // Lambdas can either be called with a list of expressions 61 | // (ex: (n1, n1) -> {}), or with a single expression 62 | // (ex: n1 -> {}) 63 | 64 | var lambdaBody *ast.BlockStmt 65 | 66 | var lambdaParameters *ast.FieldList 67 | 68 | bodyNode := node.ChildByFieldName("body") 69 | 70 | switch bodyNode.Type() { 71 | case "block": 72 | lambdaBody = ParseStmt(bodyNode, source, ctx).(*ast.BlockStmt) 73 | default: 74 | // Lambdas can be called inline without a block expression 75 | lambdaBody = &ast.BlockStmt{ 76 | List: []ast.Stmt{ 77 | &ast.ExprStmt{ 78 | X: ParseExpr(bodyNode, source, ctx), 79 | }, 80 | }, 81 | } 82 | } 83 | 84 | paramNode := node.ChildByFieldName("parameters") 85 | 86 | switch paramNode.Type() { 87 | case "inferred_parameters", "formal_parameters": 88 | lambdaParameters = ParseNode(paramNode, source, ctx).(*ast.FieldList) 89 | default: 90 | // If we can't identify the types of the parameters, then just set their 91 | // types to any 92 | lambdaParameters = &ast.FieldList{ 93 | List: []*ast.Field{ 94 | &ast.Field{ 95 | Names: []*ast.Ident{ParseExpr(paramNode, source, ctx).(*ast.Ident)}, 96 | Type: &ast.Ident{Name: "any"}, 97 | }, 98 | }, 99 | } 100 | } 101 | 102 | return &ast.FuncLit{ 103 | Type: &ast.FuncType{ 104 | Params: lambdaParameters, 105 | }, 106 | Body: lambdaBody, 107 | } 108 | case "method_reference": 109 | // This refers to manually selecting a function from a specific class and 110 | // passing it in as an argument in the `func(className::methodName)` style 111 | 112 | // For class constructors such as `Class::new`, you only get one node 113 | if node.NamedChildCount() < 2 { 114 | return &ast.SelectorExpr{ 115 | X: ParseExpr(node.NamedChild(0), source, ctx), 116 | Sel: &ast.Ident{Name: "new"}, 117 | } 118 | } 119 | 120 | return &ast.SelectorExpr{ 121 | X: ParseExpr(node.NamedChild(0), source, ctx), 122 | Sel: ParseExpr(node.NamedChild(1), source, ctx).(*ast.Ident), 123 | } 124 | case "array_initializer": 125 | // A literal that initilzes an array, such as `{1, 2, 3}` 126 | items := []ast.Expr{} 127 | for _, c := range nodeutil.NamedChildrenOf(node) { 128 | items = append(items, ParseExpr(c, source, ctx)) 129 | } 130 | 131 | // If there wasn't a type for the array specified, then use the one that has been defined 132 | if _, ok := ctx.lastType.(*ast.ArrayType); ctx.lastType != nil && ok { 133 | return &ast.CompositeLit{ 134 | Type: ctx.lastType.(*ast.ArrayType), 135 | Elts: items, 136 | } 137 | } 138 | return &ast.CompositeLit{ 139 | Elts: items, 140 | } 141 | case "method_invocation": 142 | // Methods with a selector are called as X.Sel(Args) 143 | // Otherwise, they are called as Fun(Args) 144 | if node.ChildByFieldName("object") != nil { 145 | return &ast.CallExpr{ 146 | Fun: &ast.SelectorExpr{ 147 | X: ParseExpr(node.ChildByFieldName("object"), source, ctx), 148 | Sel: ParseExpr(node.ChildByFieldName("name"), source, ctx).(*ast.Ident), 149 | }, 150 | Args: ParseNode(node.ChildByFieldName("arguments"), source, ctx).([]ast.Expr), 151 | } 152 | } 153 | return &ast.CallExpr{ 154 | Fun: ParseExpr(node.ChildByFieldName("name"), source, ctx), 155 | Args: ParseNode(node.ChildByFieldName("arguments"), source, ctx).([]ast.Expr), 156 | } 157 | case "object_creation_expression": 158 | // This is called when anything is created with a constructor 159 | 160 | objectType := node.ChildByFieldName("type") 161 | 162 | // A object can also be created with this format: 163 | // parentClass.new NestedClass() 164 | if !node.NamedChild(0).Equal(objectType) { 165 | } 166 | 167 | // Get all the arguments, and look up their types 168 | objectArguments := node.ChildByFieldName("arguments") 169 | arguments := make([]ast.Expr, objectArguments.NamedChildCount()) 170 | argumentTypes := make([]string, objectArguments.NamedChildCount()) 171 | for ind, argument := range nodeutil.NamedChildrenOf(objectArguments) { 172 | arguments[ind] = ParseExpr(argument, source, ctx) 173 | 174 | // Look up each argument and find its type 175 | if argument.Type() != "identifier" { 176 | argumentTypes[ind] = symbol.TypeOfLiteral(argument, source) 177 | } else { 178 | if localDef := ctx.localScope.FindVariable(argument.Content(source)); localDef != nil { 179 | argumentTypes[ind] = localDef.OriginalType 180 | // Otherwise, a variable may exist as a global variable 181 | } else if def := ctx.currentFile.FindField().ByOriginalName(argument.Content(source)); len(def) > 0 { 182 | argumentTypes[ind] = def[0].OriginalType 183 | } 184 | } 185 | } 186 | 187 | var constructor *symbol.Definition 188 | // Find the respective constructor, and call it 189 | if objectType.Type() == "generic_type" { 190 | constructor = ctx.currentClass.FindMethodByName(objectType.NamedChild(0).Content(source), argumentTypes) 191 | } else { 192 | constructor = ctx.currentClass.FindMethodByName(objectType.Content(source), argumentTypes) 193 | } 194 | 195 | if constructor != nil { 196 | return &ast.CallExpr{ 197 | Fun: &ast.Ident{Name: constructor.Name}, 198 | Args: arguments, 199 | } 200 | } 201 | 202 | // It is also possible that a constructor could be unresolved, so we handle 203 | // this by calling the type of the type + "Construct" at the beginning 204 | return &ast.CallExpr{ 205 | Fun: &ast.Ident{Name: "Construct" + objectType.Content(source)}, 206 | Args: arguments, 207 | } 208 | case "array_creation_expression": 209 | dimensions := []ast.Expr{} 210 | arrayType := astutil.ParseType(node.ChildByFieldName("type"), source) 211 | 212 | for _, child := range nodeutil.NamedChildrenOf(node) { 213 | if child.Type() == "dimensions_expr" { 214 | dimensions = append(dimensions, ParseExpr(child, source, ctx)) 215 | } 216 | } 217 | 218 | // TODO: Fix this to handle arrays that are declared with types, 219 | // i.e `new int[] {1, 2, 3};` 220 | if len(dimensions) == 0 { 221 | panic("Array had zero dimensions") 222 | } 223 | 224 | return GenMultiDimArray(symbol.NodeToStr(arrayType), dimensions) 225 | case "instanceof_expression": 226 | return &ast.BadExpr{} 227 | case "dimensions_expr": 228 | return ParseExpr(node.NamedChild(0), source, ctx) 229 | case "binary_expression": 230 | if node.Child(1).Content(source) == ">>>" { 231 | return &ast.CallExpr{ 232 | Fun: &ast.Ident{Name: "UnsignedRightShift"}, 233 | Args: []ast.Expr{ParseExpr(node.Child(0), source, ctx), ParseExpr(node.Child(2), source, ctx)}, 234 | } 235 | } 236 | return &ast.BinaryExpr{ 237 | X: ParseExpr(node.Child(0), source, ctx), 238 | Op: StrToToken(node.Child(1).Content(source)), 239 | Y: ParseExpr(node.Child(2), source, ctx), 240 | } 241 | case "unary_expression": 242 | return &ast.UnaryExpr{ 243 | Op: StrToToken(node.Child(0).Content(source)), 244 | X: ParseExpr(node.Child(1), source, ctx), 245 | } 246 | case "parenthesized_expression": 247 | return &ast.ParenExpr{ 248 | X: ParseExpr(node.NamedChild(0), source, ctx), 249 | } 250 | case "ternary_expression": 251 | // Ternary expressions are replaced with a function that takes in the 252 | // condition, and returns one of the two values, depending on the condition 253 | 254 | args := []ast.Expr{} 255 | for _, c := range nodeutil.NamedChildrenOf(node) { 256 | args = append(args, ParseExpr(c, source, ctx)) 257 | } 258 | return &ast.CallExpr{ 259 | Fun: &ast.Ident{Name: "ternary"}, 260 | Args: args, 261 | } 262 | case "cast_expression": 263 | // TODO: This probably should be a cast function, instead of an assertion 264 | return &ast.TypeAssertExpr{ 265 | X: ParseExpr(node.NamedChild(1), source, ctx), 266 | Type: astutil.ParseType(node.NamedChild(0), source), 267 | } 268 | case "field_access": 269 | // X.Sel 270 | obj := node.ChildByFieldName("object") 271 | 272 | if obj.Type() == "this" { 273 | def := ctx.currentClass.FindField().ByOriginalName(node.ChildByFieldName("field").Content(source)) 274 | if len(def) == 0 { 275 | // TODO: This field could not be found in the current class, because it exists in the superclass 276 | // definition for the class 277 | def = []*symbol.Definition{&symbol.Definition{ 278 | Name: node.ChildByFieldName("field").Content(source), 279 | }} 280 | } 281 | 282 | return &ast.SelectorExpr{ 283 | X: ParseExpr(node.ChildByFieldName("object"), source, ctx), 284 | Sel: &ast.Ident{Name: def[0].Name}, 285 | } 286 | } 287 | return &ast.SelectorExpr{ 288 | X: ParseExpr(obj, source, ctx), 289 | Sel: ParseExpr(node.ChildByFieldName("field"), source, ctx).(*ast.Ident), 290 | } 291 | case "array_access": 292 | return &ast.IndexExpr{ 293 | X: ParseExpr(node.NamedChild(0), source, ctx), 294 | Index: ParseExpr(node.NamedChild(1), source, ctx), 295 | } 296 | case "scoped_identifier": 297 | return ParseExpr(node.NamedChild(0), source, ctx) 298 | case "this": 299 | return &ast.Ident{Name: ShortName(ctx.className)} 300 | case "identifier": 301 | return &ast.Ident{Name: node.Content(source)} 302 | case "type_identifier": // Any reference type 303 | switch node.Content(source) { 304 | // Special case for strings, because in Go, these are primitive types 305 | case "String": 306 | return &ast.Ident{Name: "string"} 307 | } 308 | 309 | if ctx.currentFile != nil { 310 | // Look for the class locally first 311 | if localClass := ctx.currentFile.FindClass(node.Content(source)); localClass != nil { 312 | return &ast.StarExpr{ 313 | X: &ast.Ident{Name: localClass.Name}, 314 | } 315 | } 316 | } 317 | 318 | return &ast.StarExpr{ 319 | X: &ast.Ident{Name: node.Content(source)}, 320 | } 321 | case "null_literal": 322 | return &ast.Ident{Name: "nil"} 323 | case "decimal_integer_literal": 324 | literal := node.Content(source) 325 | switch literal[len(literal)-1] { 326 | case 'L': 327 | return &ast.CallExpr{Fun: &ast.Ident{Name: "int64"}, Args: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: literal[:len(literal)-1]}}} 328 | } 329 | return &ast.Ident{Name: literal} 330 | case "hex_integer_literal": 331 | return &ast.Ident{Name: node.Content(source)} 332 | case "decimal_floating_point_literal": 333 | // This is something like 1.3D or 1.3F 334 | literal := node.Content(source) 335 | switch literal[len(literal)-1] { 336 | case 'D': 337 | return &ast.CallExpr{Fun: &ast.Ident{Name: "float64"}, Args: []ast.Expr{&ast.BasicLit{Kind: token.FLOAT, Value: literal[:len(literal)-1]}}} 338 | case 'F': 339 | return &ast.CallExpr{Fun: &ast.Ident{Name: "float32"}, Args: []ast.Expr{&ast.BasicLit{Kind: token.FLOAT, Value: literal[:len(literal)-1]}}} 340 | } 341 | return &ast.Ident{Name: literal} 342 | case "string_literal": 343 | return &ast.Ident{Name: node.Content(source)} 344 | case "character_literal": 345 | return &ast.Ident{Name: node.Content(source)} 346 | case "true", "false": 347 | return &ast.Ident{Name: node.Content(source)} 348 | } 349 | panic("Unhandled expression: " + node.Type()) 350 | } 351 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | "strconv" 8 | "unicode" 9 | ) 10 | 11 | var tokens = map[string]token.Token{ 12 | "+": token.ADD, 13 | "-": token.SUB, 14 | "*": token.MUL, 15 | "/": token.QUO, 16 | "%": token.REM, 17 | 18 | "&": token.AND, 19 | "|": token.OR, 20 | "^": token.XOR, 21 | // Java bitwise complement (~) 22 | "~": token.XOR, 23 | "<<": token.SHL, 24 | ">>": token.SHR, 25 | "&^": token.AND_NOT, 26 | 27 | "+=": token.ADD_ASSIGN, 28 | "-=": token.SUB_ASSIGN, 29 | "*=": token.MUL_ASSIGN, 30 | "/=": token.QUO_ASSIGN, 31 | "%=": token.REM_ASSIGN, 32 | 33 | "&=": token.AND_ASSIGN, 34 | "|=": token.OR_ASSIGN, 35 | "^=": token.XOR_ASSIGN, 36 | "<<=": token.SHL_ASSIGN, 37 | ">>=": token.SHR_ASSIGN, 38 | "&^=": token.AND_NOT_ASSIGN, 39 | 40 | "&&": token.LAND, 41 | "||": token.LOR, 42 | "++": token.INC, 43 | "--": token.DEC, 44 | 45 | "==": token.EQL, 46 | "<": token.LSS, 47 | ">": token.GTR, 48 | "=": token.ASSIGN, 49 | "!": token.NOT, 50 | 51 | "!=": token.NEQ, 52 | "<=": token.LEQ, 53 | ">=": token.GEQ, 54 | ":=": token.DEFINE, 55 | "...": token.ELLIPSIS, 56 | } 57 | 58 | // Maps a token's representation to its token, e.g. "+" -> token.ADD 59 | func StrToToken(input string) token.Token { 60 | if outToken, known := tokens[input]; known { 61 | return outToken 62 | } 63 | panic(fmt.Errorf("Unknown token for [%v]", input)) 64 | } 65 | 66 | // ShortName returns the short-name representation of a class's name for use 67 | // in methods and construtors 68 | // Ex: Test -> ts 69 | func ShortName(longName string) string { 70 | if len(longName) == 0 { 71 | return "" 72 | } 73 | return string(unicode.ToLower(rune(longName[0]))) + string(unicode.ToLower(rune(longName[len(longName)-1]))) 74 | } 75 | 76 | // GenStruct is a utility method for generating the ast representation of 77 | // a struct, given its name and fields 78 | func GenStruct(structName string, structFields *ast.FieldList) ast.Decl { 79 | return &ast.GenDecl{ 80 | Tok: token.TYPE, 81 | Specs: []ast.Spec{ 82 | &ast.TypeSpec{ 83 | Name: &ast.Ident{ 84 | Name: structName, 85 | }, 86 | Type: &ast.StructType{ 87 | Fields: structFields, 88 | }, 89 | }, 90 | }, 91 | } 92 | } 93 | 94 | func genType(remaining []string) ast.Expr { 95 | if len(remaining) == 1 { 96 | return &ast.UnaryExpr{ 97 | Op: token.TILDE, 98 | X: &ast.Ident{Name: remaining[0]}, 99 | } 100 | } 101 | return &ast.BinaryExpr{ 102 | X: genType(remaining[1:]), 103 | Op: token.OR, 104 | Y: genType(remaining[:1]), 105 | } 106 | } 107 | 108 | func GenTypeInterface(name string, types []string) ast.Decl { 109 | return &ast.GenDecl{ 110 | Tok: token.TYPE, 111 | Specs: []ast.Spec{ 112 | &ast.TypeSpec{ 113 | Name: &ast.Ident{Name: name}, 114 | Type: &ast.InterfaceType{ 115 | Methods: &ast.FieldList{ 116 | List: []*ast.Field{ 117 | &ast.Field{ 118 | Type: genType(types), 119 | }, 120 | }, 121 | }, 122 | }, 123 | }, 124 | }, 125 | } 126 | } 127 | 128 | func GenInterface(name string, methods *ast.FieldList) ast.Decl { 129 | return &ast.GenDecl{ 130 | Tok: token.TYPE, 131 | Specs: []ast.Spec{ 132 | &ast.TypeSpec{ 133 | Name: &ast.Ident{Name: name}, 134 | Type: &ast.InterfaceType{ 135 | Methods: methods, 136 | }, 137 | }, 138 | }, 139 | } 140 | } 141 | 142 | func GenMultiDimArray(arrayType string, dimensions []ast.Expr) ast.Expr { 143 | if len(dimensions) == 1 { 144 | return &ast.CallExpr{ 145 | Fun: &ast.Ident{Name: "make"}, 146 | Args: append([]ast.Expr{&ast.Ident{Name: arrayType}}, dimensions...), 147 | } 148 | } 149 | 150 | // arr := make([][][]int, 2) 151 | base := &ast.AssignStmt{ 152 | Tok: token.DEFINE, 153 | Lhs: []ast.Expr{&ast.Ident{Name: "arr"}}, 154 | Rhs: []ast.Expr{ 155 | makeExpression(genArrayType(arrayType, len(dimensions)), dimensions[0]), 156 | }, 157 | } 158 | 159 | indexes := []string{"ind"} 160 | 161 | var body, currentDimension *ast.RangeStmt 162 | 163 | for offset := range dimensions[1:] { 164 | nextDim := &ast.RangeStmt{ 165 | Key: &ast.Ident{Name: indexes[len(indexes)-1]}, 166 | Tok: token.DEFINE, 167 | X: multiArrayAccess("arr", indexes[:len(indexes)-1]), 168 | Body: &ast.BlockStmt{ 169 | List: []ast.Stmt{ 170 | &ast.AssignStmt{ 171 | Tok: token.ASSIGN, 172 | Lhs: []ast.Expr{multiArrayAccess("arr", indexes)}, 173 | Rhs: []ast.Expr{makeExpression(genArrayType(arrayType, len(dimensions)-(offset+1)), dimensions[offset+1])}, 174 | }, 175 | }, 176 | }, 177 | } 178 | 179 | if body == nil { 180 | body = nextDim 181 | currentDimension = body 182 | } else { 183 | currentDimension.Body.List = append(currentDimension.Body.List, nextDim) 184 | currentDimension = currentDimension.Body.List[len(currentDimension.Body.List)-1].(*ast.RangeStmt) 185 | } 186 | 187 | indexes = append(indexes, indexes[len(indexes)-1]+strconv.Itoa(offset)) 188 | } 189 | 190 | return &ast.CallExpr{ 191 | Fun: &ast.FuncLit{ 192 | Type: &ast.FuncType{ 193 | Results: &ast.FieldList{ 194 | List: []*ast.Field{ 195 | &ast.Field{ 196 | Type: genArrayType(arrayType, len(dimensions)), 197 | }, 198 | }, 199 | }, 200 | }, 201 | Body: &ast.BlockStmt{ 202 | List: []ast.Stmt{ 203 | base, 204 | body, 205 | &ast.ReturnStmt{Results: []ast.Expr{&ast.Ident{Name: "arr"}}}, 206 | }, 207 | }, 208 | }, 209 | } 210 | } 211 | 212 | func multiArrayAccess(arrName string, dims []string) ast.Expr { 213 | var arr ast.Expr = &ast.Ident{Name: arrName} 214 | for _, dim := range dims { 215 | arr = &ast.IndexExpr{X: arr, Index: &ast.Ident{Name: dim}} 216 | } 217 | return arr 218 | } 219 | 220 | func genArrayType(arrayType string, depth int) ast.Expr { 221 | var arrayDims ast.Expr = &ast.Ident{Name: arrayType} 222 | for i := 0; i < depth; i++ { 223 | arrayDims = &ast.ArrayType{Elt: arrayDims} 224 | } 225 | return arrayDims 226 | } 227 | 228 | // makeExpression constructs an array with the `make` keyword 229 | func makeExpression(dims, expr ast.Expr) *ast.CallExpr { 230 | return &ast.CallExpr{ 231 | Fun: &ast.Ident{Name: "make"}, 232 | Args: []ast.Expr{ 233 | dims, 234 | expr, 235 | }, 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NickyBoy89/java2go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/sirupsen/logrus v1.9.0 7 | github.com/smacker/go-tree-sitter v0.0.0-20230113054119-af7e2ef5fed6 8 | golang.org/x/exp v0.0.0-20220312040426-20fd27f61765 9 | ) 10 | 11 | require ( 12 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 13 | github.com/spf13/cobra v1.6.1 // indirect 14 | github.com/spf13/pflag v1.0.5 // indirect 15 | golang.org/x/sys v0.5.0 // indirect 16 | gopkg.in/yaml.v2 v2.4.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 6 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 10 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 11 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 12 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 13 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 14 | github.com/smacker/go-tree-sitter v0.0.0-20220323032108-9170be4a682c h1:DyKhBbcwOEbQ5zJXqsIU631DZgdtnUJtUYOA1PxF5QM= 15 | github.com/smacker/go-tree-sitter v0.0.0-20220323032108-9170be4a682c/go.mod h1:EiUuVMUfLQj8Sul+S8aKWJwQy7FRYnJCO2EWzf8F5hk= 16 | github.com/smacker/go-tree-sitter v0.0.0-20220421092837-ec55f7cfeaf4 h1:UFOHRX5nrxNCVORhicjy31nzSVt9rEjf/YRcx2Dc3MM= 17 | github.com/smacker/go-tree-sitter v0.0.0-20220421092837-ec55f7cfeaf4/go.mod h1:EiUuVMUfLQj8Sul+S8aKWJwQy7FRYnJCO2EWzf8F5hk= 18 | github.com/smacker/go-tree-sitter v0.0.0-20230113054119-af7e2ef5fed6 h1:FX6rwoAcx8JXrO9WHbV2yxBCgH9LlGT2LYWPi/4jtOE= 19 | github.com/smacker/go-tree-sitter v0.0.0-20230113054119-af7e2ef5fed6/go.mod h1:q99oHDsbP0xRwmn7Vmob8gbSMNyvJ83OauXPSuHQuKE= 20 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 21 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 22 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 23 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 25 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 26 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 27 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 28 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 29 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 30 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 31 | github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= 32 | github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 33 | golang.org/x/exp v0.0.0-20220312040426-20fd27f61765 h1:p80Xjx7+xLY3+FFWW3KSo34VwQwWFdSKANfks5INL2g= 34 | golang.org/x/exp v0.0.0-20220312040426-20fd27f61765/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 35 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= 37 | golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 39 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 42 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 45 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 46 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 47 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 48 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 49 | -------------------------------------------------------------------------------- /java2go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "go/ast" 7 | "go/printer" 8 | "go/token" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "sync" 14 | 15 | "github.com/NickyBoy89/java2go/parsing" 16 | "github.com/NickyBoy89/java2go/symbol" 17 | log "github.com/sirupsen/logrus" 18 | ) 19 | 20 | // Stores a global list of Java annotations to exclude from the generated code 21 | var excludedAnnotations = make(map[string]bool) 22 | 23 | // Command-line arguments 24 | var ( 25 | writeFiles bool 26 | dryRun bool 27 | displayAST bool 28 | symbolAware bool 29 | parseFilesSynchronously bool 30 | ) 31 | 32 | var ( 33 | outputDirectory string 34 | ignoredAnnotations string 35 | ) 36 | 37 | func main() { 38 | flag.BoolVar(&writeFiles, "w", false, "Whether to write the files to disk instead of stdout") 39 | flag.BoolVar(&dryRun, "q", false, "Don't write to stdout on successful parse") 40 | flag.BoolVar(&displayAST, "ast", false, "Print out go's pretty-printed ast, instead of source code") 41 | flag.BoolVar(&parseFilesSynchronously, "sync", false, "Parse the files one by one, instead of in parallel") 42 | flag.BoolVar(&symbolAware, "symbols", true, `Whether the program is aware of the symbols of the parsed code 43 | Results in better code generation, but can be disabled for a more direct translation 44 | or to fix crashes with the symbol handling`, 45 | ) 46 | flag.StringVar(&outputDirectory, "output", ".", "Specify a directory for the generated files") 47 | flag.StringVar(&ignoredAnnotations, "exclude-annotations", "", "A comma-separated list of annotations to exclude from the final code generation") 48 | 49 | flag.Parse() 50 | 51 | for _, annotation := range strings.Split(ignoredAnnotations, ",") { 52 | excludedAnnotations[annotation] = true 53 | } 54 | 55 | // All the files to parse 56 | var files []parsing.SourceFile 57 | 58 | log.Info("Collecting files...") 59 | 60 | // Collect all the files and read them into memory 61 | for _, dirName := range flag.Args() { 62 | sources, err := parsing.ReadSourcesInDir(dirName) 63 | if err != nil { 64 | log.WithField("error", err).Fatal("Error reading directory") 65 | } 66 | files = append(files, sources...) 67 | } 68 | 69 | if len(files) == 0 { 70 | log.Warn("No files specified to convert") 71 | } 72 | 73 | // Parse the ASTs of all the files 74 | 75 | log.Info("Parsing ASTs...") 76 | 77 | var wg sync.WaitGroup 78 | wg.Add(len(files)) 79 | 80 | for index := range files { 81 | parseFunc := func(ind int) { 82 | if err := files[ind].ParseAST(); err != nil { 83 | log.WithField("error", err).Error("Error parsing AST") 84 | } 85 | wg.Done() 86 | } 87 | 88 | if parseFilesSynchronously { 89 | parseFunc(index) 90 | } else { 91 | go parseFunc(index) 92 | } 93 | } 94 | 95 | // We might still have some parsing jobs, so wait on them 96 | wg.Wait() 97 | 98 | for _, file := range files { 99 | if file.Ast == nil { 100 | panic("Not all files have asts") 101 | } 102 | } 103 | 104 | // Generate the symbol tables for the files 105 | if symbolAware { 106 | log.Info("Generating symbol tables...") 107 | 108 | for index, file := range files { 109 | if file.Ast.HasError() { 110 | log.WithFields(log.Fields{ 111 | "fileName": file.Name, 112 | }).Warn("AST parse error in file, skipping file") 113 | continue 114 | } 115 | 116 | symbols := files[index].ParseSymbols() 117 | // Add the symbols to the global symbol table 118 | symbol.AddSymbolsToPackage(symbols) 119 | } 120 | 121 | // Go back through the symbol tables and fill in anything that could not be resolved 122 | 123 | log.Info("Resolving symbols...") 124 | 125 | for _, file := range files { 126 | if !file.Ast.HasError() { 127 | ResolveFile(file) 128 | } 129 | } 130 | } 131 | 132 | // Transpile the files 133 | 134 | log.Info("Converting files...") 135 | 136 | for _, file := range files { 137 | if dryRun { 138 | log.Infof("Not converting file \"%s\"", file.Name) 139 | continue 140 | } 141 | 142 | log.Infof("Converting file \"%s\"", file.Name) 143 | 144 | // Write to stdout by default 145 | var output io.Writer = os.Stdout 146 | if writeFiles { 147 | // Write to a `.go` file in the same directory 148 | outputFile := fmt.Sprintf("%s/%s", 149 | outputDirectory, 150 | strings.TrimSuffix(file.Name, filepath.Ext(file.Name))+".go", 151 | ) 152 | 153 | err := os.MkdirAll(outputDirectory, 0755) 154 | if err != nil { 155 | log.WithFields(log.Fields{ 156 | "error": err, 157 | "path": outputFile, 158 | }).Panic("Error creating output directory") 159 | } 160 | 161 | // Write the output to a file 162 | output, err = os.Create(outputFile) 163 | if err != nil { 164 | log.WithFields(log.Fields{ 165 | "error": err, 166 | "file": outputFile, 167 | }).Panic("Error creating output file") 168 | } 169 | } 170 | 171 | // The converted AST, in Go's AST representation 172 | var initialContext Ctx 173 | if symbolAware { 174 | initialContext.currentFile = file.Symbols 175 | initialContext.currentClass = file.Symbols.BaseClass 176 | } 177 | 178 | parsed := ParseNode(file.Ast, file.Source, initialContext).(ast.Node) 179 | 180 | // Print the generated AST 181 | if displayAST { 182 | ast.Print(token.NewFileSet(), parsed) 183 | } 184 | 185 | // Output the parsed AST, into the source specified earlier 186 | if err := printer.Fprint(output, token.NewFileSet(), parsed); err != nil { 187 | log.WithFields(log.Fields{ 188 | "error": err, 189 | }).Panic("Error printing generated code") 190 | } 191 | 192 | if writeFiles { 193 | output.(*os.File).Close() 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /nodeutil/assertions.go: -------------------------------------------------------------------------------- 1 | package nodeutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | sitter "github.com/smacker/go-tree-sitter" 7 | ) 8 | 9 | func AssertTypeIs(node *sitter.Node, expectedType string) { 10 | if node.Type() != expectedType { 11 | panic(fmt.Sprintf("assertion failed: Type of node differs from expected: %s, got: %s", expectedType, node.Type())) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nodeutil/node_helpers.go: -------------------------------------------------------------------------------- 1 | package nodeutil 2 | 3 | import sitter "github.com/smacker/go-tree-sitter" 4 | 5 | // NamedChildrenOf gets all named children of a given node 6 | func NamedChildrenOf(node *sitter.Node) []*sitter.Node { 7 | count := int(node.NamedChildCount()) 8 | children := make([]*sitter.Node, count) 9 | for i := 0; i < count; i++ { 10 | children[i] = node.NamedChild(i) 11 | } 12 | return children 13 | } 14 | 15 | // UnnamedChildrenOf gets all the named + unnamed children of a given node 16 | func UnnamedChildrenOf(node *sitter.Node) []*sitter.Node { 17 | count := int(node.ChildCount()) 18 | children := make([]*sitter.Node, count) 19 | for i := 0; i < count; i++ { 20 | children[i] = node.Child(i) 21 | } 22 | return children 23 | } 24 | -------------------------------------------------------------------------------- /parsing/parsing_tools.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | const JavaExt = ".java" 10 | 11 | func ReadSourcesInDir(directoryName string) ([]SourceFile, error) { 12 | sources := []SourceFile{} 13 | 14 | if _, err := os.Stat(directoryName); err != nil { 15 | return sources, err 16 | } 17 | 18 | if err := filepath.WalkDir(directoryName, fs.WalkDirFunc( 19 | func(path string, d fs.DirEntry, err error) error { 20 | 21 | // Only include java files 22 | if filepath.Ext(path) == JavaExt && !d.IsDir() { 23 | sourceCode, err := os.ReadFile(path) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | sources = append(sources, SourceFile{ 29 | Name: path, 30 | Source: sourceCode, 31 | }) 32 | } 33 | 34 | return nil 35 | }, 36 | )); err != nil { 37 | return nil, err 38 | } 39 | 40 | return sources, nil 41 | } 42 | 43 | func ParseASTs(file SourceFile) { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /parsing/source_file.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/NickyBoy89/java2go/symbol" 8 | sitter "github.com/smacker/go-tree-sitter" 9 | "github.com/smacker/go-tree-sitter/java" 10 | ) 11 | 12 | type SourceFile struct { 13 | Name string 14 | Source []byte 15 | Ast *sitter.Node 16 | Symbols *symbol.FileScope 17 | } 18 | 19 | func (file SourceFile) String() string { 20 | return fmt.Sprintf("SourceFile { Name: %s, Ast: %v, Symbols: %v }", file.Name, file.Ast, file.Symbols) 21 | } 22 | 23 | func (file *SourceFile) ParseAST() error { 24 | parser := sitter.NewParser() 25 | parser.SetLanguage(java.GetLanguage()) 26 | tree, err := parser.ParseCtx(context.Background(), nil, file.Source) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | file.Ast = tree.RootNode() 32 | return nil 33 | } 34 | 35 | func (file *SourceFile) ParseSymbols() *symbol.FileScope { 36 | symbols := symbol.ParseSymbols(file.Ast, file.Source) 37 | file.Symbols = symbols 38 | return symbols 39 | } 40 | -------------------------------------------------------------------------------- /resolve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/NickyBoy89/java2go/parsing" 7 | "github.com/NickyBoy89/java2go/symbol" 8 | ) 9 | 10 | func ResolveFile(file parsing.SourceFile) { 11 | ResolveClass(file.Symbols.BaseClass, file) 12 | for _, subclass := range file.Symbols.BaseClass.Subclasses { 13 | ResolveClass(subclass, file) 14 | } 15 | } 16 | 17 | func ResolveClass(class *symbol.ClassScope, file parsing.SourceFile) { 18 | // Resolve all the fields in that respective class 19 | for _, field := range class.Fields { 20 | 21 | // Since a private global variable is able to be accessed in the package, it must be renamed 22 | // to avoid conflicts with other global variables 23 | 24 | packageScope := symbol.GlobalScope.FindPackage(file.Symbols.Package) 25 | 26 | symbol.ResolveDefinition(field, file.Symbols) 27 | 28 | // Rename the field if its name conflits with any keyword 29 | for i := 0; symbol.IsReserved(field.Name) || 30 | len(packageScope.ExcludeFile(class.Class.Name).FindStaticField().ByName(field.Name)) > 0; i++ { 31 | field.Rename(field.Name + strconv.Itoa(i)) 32 | } 33 | } 34 | 35 | // Resolve all the methods 36 | for _, method := range class.Methods { 37 | // Resolve the return type, as well as the body of the method 38 | symbol.ResolveChildren(method, file.Symbols) 39 | 40 | // Comparison compares the method against the found method 41 | // This tests for a method of the same name, but with different 42 | // aspects of it, so that it can be identified as a duplicate 43 | comparison := func(d *symbol.Definition) bool { 44 | // The names must match, but everything else must be different 45 | if method.Name != d.Name { 46 | return false 47 | } 48 | 49 | // Size of parameters do not match 50 | if len(method.Parameters) != len(d.Parameters) { 51 | return true 52 | } 53 | 54 | // Go through the types and check to see if they differ 55 | for index, param := range method.Parameters { 56 | if param.OriginalType != d.Parameters[index].OriginalType { 57 | return true 58 | } 59 | } 60 | 61 | // Both methods are equal, skip this method since it is likely 62 | // the same method that we are trying to find duplicates of 63 | return false 64 | } 65 | 66 | for i := 0; symbol.IsReserved(method.Name) || len(class.FindMethod().By(comparison)) > 0; i++ { 67 | method.Rename(method.Name + strconv.Itoa(i)) 68 | } 69 | // Resolve all the paramters of the method 70 | for _, param := range method.Parameters { 71 | symbol.ResolveDefinition(param, file.Symbols) 72 | 73 | for i := 0; symbol.IsReserved(param.Name); i++ { 74 | param.Rename(param.Name + strconv.Itoa(i)) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /statement.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | 8 | "github.com/NickyBoy89/java2go/astutil" 9 | "github.com/NickyBoy89/java2go/nodeutil" 10 | log "github.com/sirupsen/logrus" 11 | sitter "github.com/smacker/go-tree-sitter" 12 | ) 13 | 14 | func ParseStmt(node *sitter.Node, source []byte, ctx Ctx) ast.Stmt { 15 | if stmt := TryParseStmt(node, source, ctx); stmt != nil { 16 | return stmt 17 | } 18 | panic(fmt.Errorf("Unhandled stmt type: %v", node.Type())) 19 | } 20 | 21 | func TryParseStmt(node *sitter.Node, source []byte, ctx Ctx) ast.Stmt { 22 | switch node.Type() { 23 | case "ERROR": 24 | log.WithFields(log.Fields{ 25 | "parsed": node.Content(source), 26 | "className": ctx.className, 27 | }).Warn("Statement parse error") 28 | return &ast.BadStmt{} 29 | case "comment": 30 | return &ast.BadStmt{} 31 | case "local_variable_declaration": 32 | variableType := astutil.ParseType(node.ChildByFieldName("type"), source) 33 | variableDeclarator := node.ChildByFieldName("declarator") 34 | 35 | // If a variable is being declared, but not set to a value 36 | // Ex: `int value;` 37 | if variableDeclarator.NamedChildCount() == 1 { 38 | return &ast.DeclStmt{ 39 | Decl: &ast.GenDecl{ 40 | Tok: token.VAR, 41 | Specs: []ast.Spec{ 42 | &ast.ValueSpec{ 43 | Names: []*ast.Ident{ParseExpr(variableDeclarator.ChildByFieldName("name"), source, ctx).(*ast.Ident)}, 44 | Type: variableType, 45 | }, 46 | }, 47 | }, 48 | } 49 | } 50 | 51 | ctx.lastType = variableType 52 | 53 | declaration := ParseStmt(variableDeclarator, source, ctx).(*ast.AssignStmt) 54 | 55 | // Now, if a variable is assigned to `null`, we can't infer its type, so 56 | // don't throw out the type information associated with it 57 | var containsNull bool 58 | 59 | // Go through the values and see if there is a `null_literal` 60 | for _, child := range nodeutil.NamedChildrenOf(variableDeclarator) { 61 | if child.Type() == "null_literal" { 62 | containsNull = true 63 | break 64 | } 65 | } 66 | 67 | names := make([]*ast.Ident, len(declaration.Lhs)) 68 | for ind, decl := range declaration.Lhs { 69 | names[ind] = decl.(*ast.Ident) 70 | } 71 | 72 | // If the declaration contains null, declare it with the `var` keyword instead 73 | // of implicitly 74 | if containsNull { 75 | return &ast.DeclStmt{ 76 | Decl: &ast.GenDecl{ 77 | Tok: token.VAR, 78 | Specs: []ast.Spec{ 79 | &ast.ValueSpec{ 80 | Names: names, 81 | Type: variableType, 82 | Values: declaration.Rhs, 83 | }, 84 | }, 85 | }, 86 | } 87 | } 88 | 89 | return declaration 90 | case "variable_declarator": 91 | var names, values []ast.Expr 92 | 93 | // If there is only one node, then that node is just a name 94 | if node.NamedChildCount() == 1 { 95 | names = append(names, ParseExpr(node.NamedChild(0), source, ctx)) 96 | } 97 | 98 | // Loop through every pair of name and value 99 | for ind := 0; ind < int(node.NamedChildCount())-1; ind += 2 { 100 | names = append(names, ParseExpr(node.NamedChild(ind), source, ctx)) 101 | values = append(values, ParseExpr(node.NamedChild(ind+1), source, ctx)) 102 | } 103 | 104 | return &ast.AssignStmt{Lhs: names, Tok: token.DEFINE, Rhs: values} 105 | case "assignment_expression": 106 | assignVar := ParseExpr(node.Child(0), source, ctx) 107 | assignVal := ParseExpr(node.Child(2), source, ctx) 108 | 109 | // Unsigned right shift 110 | if node.Child(1).Content(source) == ">>>=" { 111 | return &ast.ExprStmt{X: &ast.CallExpr{ 112 | Fun: &ast.Ident{Name: "UnsignedRightShiftAssignment"}, 113 | Args: []ast.Expr{assignVar, assignVal}, 114 | }} 115 | } 116 | 117 | return &ast.AssignStmt{ 118 | Lhs: []ast.Expr{assignVar}, 119 | Tok: StrToToken(node.Child(1).Content(source)), 120 | Rhs: []ast.Expr{assignVal}, 121 | } 122 | case "update_expression": 123 | if node.Child(0).IsNamed() { 124 | return &ast.IncDecStmt{ 125 | X: ParseExpr(node.Child(0), source, ctx), 126 | Tok: StrToToken(node.Child(1).Content(source)), 127 | } 128 | } 129 | 130 | return &ast.IncDecStmt{ 131 | X: ParseExpr(node.Child(1), source, ctx), 132 | Tok: StrToToken(node.Child(0).Content(source)), 133 | } 134 | case "resource_specification": 135 | return ParseStmt(node.NamedChild(0), source, ctx) 136 | case "resource": 137 | var offset int 138 | if node.NamedChild(0).Type() == "modifiers" { 139 | offset = 1 140 | } 141 | return &ast.AssignStmt{ 142 | Lhs: []ast.Expr{ParseExpr(node.NamedChild(1+offset), source, ctx)}, 143 | Tok: token.DEFINE, 144 | Rhs: []ast.Expr{ParseExpr(node.NamedChild(2+offset), source, ctx)}, 145 | } 146 | case "method_invocation": 147 | return &ast.ExprStmt{X: ParseExpr(node, source, ctx)} 148 | case "constructor_body", "block": 149 | body := &ast.BlockStmt{} 150 | for _, line := range nodeutil.NamedChildrenOf(node) { 151 | if line.Type() == "comment" { 152 | continue 153 | } 154 | if stmt := TryParseStmt(line, source, ctx); stmt != nil { 155 | body.List = append(body.List, stmt) 156 | } else { 157 | // Try statements are ignored, so they return a list of statements 158 | body.List = append(body.List, ParseNode(line, source, ctx).([]ast.Stmt)...) 159 | } 160 | } 161 | return body 162 | case "expression_statement": 163 | if stmt := TryParseStmt(node.NamedChild(0), source, ctx); stmt != nil { 164 | return stmt 165 | } 166 | return &ast.ExprStmt{X: ParseExpr(node.NamedChild(0), source, ctx)} 167 | case "explicit_constructor_invocation": 168 | // This is when a constructor calls another constructor with the use of 169 | // something such as `this(args...)` 170 | return &ast.ExprStmt{ 171 | &ast.CallExpr{ 172 | Fun: &ast.Ident{Name: "New" + ctx.className}, 173 | Args: ParseNode(node.NamedChild(1), source, ctx).([]ast.Expr), 174 | }, 175 | } 176 | case "return_statement": 177 | if node.NamedChildCount() < 1 { 178 | return &ast.ReturnStmt{Results: []ast.Expr{}} 179 | } 180 | return &ast.ReturnStmt{Results: []ast.Expr{ParseExpr(node.NamedChild(0), source, ctx)}} 181 | case "labeled_statement": 182 | return &ast.LabeledStmt{ 183 | Label: ParseExpr(node.NamedChild(0), source, ctx).(*ast.Ident), 184 | Stmt: ParseStmt(node.NamedChild(1), source, ctx), 185 | } 186 | case "break_statement": 187 | if node.NamedChildCount() > 0 { 188 | return &ast.BranchStmt{Tok: token.BREAK, Label: ParseExpr(node.NamedChild(0), source, ctx).(*ast.Ident)} 189 | } 190 | return &ast.BranchStmt{Tok: token.BREAK} 191 | case "continue_statement": 192 | if node.NamedChildCount() > 0 { 193 | return &ast.BranchStmt{Tok: token.CONTINUE, Label: ParseExpr(node.NamedChild(0), source, ctx).(*ast.Ident)} 194 | } 195 | return &ast.BranchStmt{Tok: token.CONTINUE} 196 | case "throw_statement": 197 | return &ast.ExprStmt{X: &ast.CallExpr{ 198 | Fun: &ast.Ident{Name: "panic"}, 199 | Args: []ast.Expr{ParseExpr(node.NamedChild(0), source, ctx)}, 200 | }} 201 | case "if_statement": 202 | var other ast.Stmt 203 | if node.ChildByFieldName("alternative") != nil { 204 | other = ParseStmt(node.ChildByFieldName("alternative"), source, ctx) 205 | } 206 | 207 | // If the `if` statement is inline, replace the line with a full block 208 | var body ast.Stmt = ParseStmt(node.ChildByFieldName("consequence"), source, ctx) 209 | if _, ok := body.(*ast.BlockStmt); !ok { 210 | body = &ast.BlockStmt{List: []ast.Stmt{ 211 | body, 212 | }} 213 | } 214 | 215 | return &ast.IfStmt{ 216 | Cond: ParseExpr(node.ChildByFieldName("condition"), source, ctx), 217 | Body: body.(*ast.BlockStmt), 218 | Else: other, 219 | } 220 | case "enhanced_for_statement": 221 | // An enhanced for statement has the following fields: 222 | // variables for the variable being declared (ex: int n) 223 | // then the expression that is being ranged over 224 | // and finally, the block of the expression 225 | 226 | total := int(node.NamedChildCount()) 227 | 228 | return &ast.RangeStmt{ 229 | // We don't need the type of the variable for the range expression 230 | Key: &ast.Ident{Name: "_"}, 231 | Value: ParseExpr(node.NamedChild(total-3), source, ctx), 232 | Tok: token.DEFINE, 233 | X: ParseExpr(node.NamedChild(total-2), source, ctx), 234 | Body: ParseStmt(node.NamedChild(total-1), source, ctx).(*ast.BlockStmt), 235 | } 236 | case "for_statement": 237 | var init, post ast.Stmt 238 | if node.ChildByFieldName("init") != nil { 239 | init = ParseStmt(node.ChildByFieldName("init"), source, ctx) 240 | } 241 | if node.ChildByFieldName("update") != nil { 242 | post = ParseStmt(node.ChildByFieldName("update"), source, ctx) 243 | } 244 | var cond ast.Expr 245 | if node.ChildByFieldName("condition") != nil { 246 | cond = ParseExpr(node.ChildByFieldName("condition"), source, ctx) 247 | } 248 | 249 | return &ast.ForStmt{ 250 | Init: init, 251 | Cond: cond, 252 | Post: post, 253 | Body: ParseStmt(node.ChildByFieldName("body"), source, ctx).(*ast.BlockStmt), 254 | } 255 | case "while_statement": 256 | return &ast.ForStmt{ 257 | Cond: ParseExpr(node.NamedChild(0), source, ctx), 258 | Body: ParseStmt(node.NamedChild(1), source, ctx).(*ast.BlockStmt), 259 | } 260 | case "do_statement": 261 | // A do statement is handled as a blank for loop with the condition 262 | // inserted as a break condition in the final part of the loop 263 | body := ParseStmt(node.NamedChild(0), source, ctx).(*ast.BlockStmt) 264 | 265 | body.List = append(body.List, &ast.IfStmt{ 266 | Cond: &ast.UnaryExpr{ 267 | X: &ast.ParenExpr{ 268 | X: ParseExpr(node.NamedChild(1), source, ctx), 269 | }, 270 | }, 271 | Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, 272 | }) 273 | 274 | return &ast.ForStmt{ 275 | Body: body, 276 | } 277 | case "switch_statement": 278 | return &ast.SwitchStmt{ 279 | Tag: ParseExpr(node.NamedChild(0), source, ctx), 280 | Body: ParseStmt(node.NamedChild(1), source, ctx).(*ast.BlockStmt), 281 | } 282 | case "switch_block": 283 | switchBlock := &ast.BlockStmt{} 284 | var currentCase *ast.CaseClause 285 | for _, c := range nodeutil.NamedChildrenOf(node) { 286 | switch c.Type() { 287 | case "switch_label": 288 | // When a new switch label comes, append it to the switch block 289 | if currentCase != nil { 290 | switchBlock.List = append(switchBlock.List, currentCase) 291 | } 292 | currentCase = ParseNode(c, source, ctx).(*ast.CaseClause) 293 | default: 294 | if exprs := TryParseStmts(c, source, ctx); exprs != nil { 295 | currentCase.Body = append(currentCase.Body, exprs...) 296 | } else { 297 | currentCase.Body = append(currentCase.Body, ParseStmt(c, source, ctx)) 298 | } 299 | } 300 | } 301 | 302 | return switchBlock 303 | } 304 | return nil 305 | } 306 | 307 | func ParseStmts(node *sitter.Node, source []byte, ctx Ctx) []ast.Stmt { 308 | if stmts := TryParseStmts(node, source, ctx); stmts != nil { 309 | return stmts 310 | } 311 | panic(fmt.Errorf("Unhandled stmts type: %v", node.Type())) 312 | } 313 | 314 | func TryParseStmts(node *sitter.Node, source []byte, ctx Ctx) []ast.Stmt { 315 | switch node.Type() { 316 | case "assignment_expression": 317 | if stmts, ok := ParseNode(node, source, ctx).([]ast.Stmt); ok { 318 | return stmts 319 | } 320 | case "try_statement": 321 | if stmts, ok := ParseNode(node, source, ctx).([]ast.Stmt); ok { 322 | return stmts 323 | } 324 | } 325 | return nil 326 | } 327 | -------------------------------------------------------------------------------- /stdjava/README.md: -------------------------------------------------------------------------------- 1 | # stdjava is a Go implementation of some of the Java-isms used when parsing the codebase 2 | 3 | Currently, this includes: 4 | * A generic `Ternary` function that takes in a condition, and outputs one of the two results 5 | * Unsigned right shift (`>>>=` and `>>>`), which does right shifts, but fills the top bits with zeroes, instead of being sign-dependent 6 | * Java's string `hashCode` function 7 | * The `Optional` type 8 | -------------------------------------------------------------------------------- /stdjava/common.go: -------------------------------------------------------------------------------- 1 | package stdjava 2 | 3 | import ( 4 | "math" 5 | 6 | "golang.org/x/exp/constraints" 7 | ) 8 | 9 | // Ternary represents Java's ternary operator (condition ? result1 : result2) 10 | func Ternary[T any](condition bool, result1, result2 T) T { 11 | if condition { 12 | return result1 13 | } 14 | return result2 15 | } 16 | 17 | // UnsignedRightShift is an implementation of Java's unsigned right shift 18 | // operation where a number is shifted over the number of times specified, but 19 | // the topmost bits are always filled in with zeroes 20 | func UnsignedRightShift[V, A constraints.Integer](value V, amount A) V { 21 | return V(uint32(value) >> amount) 22 | } 23 | 24 | // UnsignedRightShiftAssignment represents a right-shift assignment (`>>>=`) 25 | // where a value is assigned the result of an unsigned right shift 26 | func UnsignedRightShiftAssignment[A any, V constraints.Integer](assignTo *A, value V) { 27 | // TODO: Fix this conversion hack, and change the function to take proper values 28 | *assignTo = interface{}(UnsignedRightShift(value, 2)).(A) 29 | } 30 | 31 | // HashCode is an implementation of Java's String `hashCode` method 32 | func HashCode(s string) int { 33 | var total int 34 | n := len(s) 35 | for ind, char := range s { 36 | total += int(char) * int(math.Pow(float64(31), float64(n-(ind+1)))) 37 | } 38 | return total 39 | } 40 | 41 | // MultiDimensionArray constructs an array with two dimensions 42 | func MultiDimensionArray[T any](val []T, dims ...int) [][]T { 43 | arr := make([][]T, dims[0]) 44 | for ind := range arr { 45 | arr[ind] = make([]T, dims[1]) 46 | } 47 | return arr 48 | } 49 | 50 | // MultiDimensionArray3 constructs an array with three dimensions 51 | func MultiDimensionArray3[T any](val [][]T, dims ...int) [][][]T { 52 | arr := make([][][]T, dims[0]) 53 | for ind := range arr { 54 | arr[ind] = MultiDimensionArray([]T{}, dims[1:]...) 55 | } 56 | return arr 57 | } 58 | -------------------------------------------------------------------------------- /stdjava/hashcode_test.go: -------------------------------------------------------------------------------- 1 | package stdjava 2 | 3 | import "testing" 4 | 5 | func TestBasicStrings(t *testing.T) { 6 | in := "Hello" 7 | if HashCode(in) != 69609650 { 8 | t.Errorf("Expected the hash to be 69609650. Got %d", HashCode(in)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /stdjava/multidim_test.go: -------------------------------------------------------------------------------- 1 | package stdjava 2 | 3 | import "testing" 4 | 5 | func TestSimpleGrid(t *testing.T) { 6 | arr := MultiDimensionArray([]string{}, 3, 5) 7 | if len(arr) != 3 { 8 | t.Errorf("Got %d rows, expected %d", len(arr), 3) 9 | } 10 | if len(arr[0]) != 5 { 11 | t.Errorf("Got %d cols, expected %d", len(arr[0]), 5) 12 | } 13 | } 14 | 15 | func TestAdvancedGrid(t *testing.T) { 16 | arr := MultiDimensionArray3([][]int{}, 1, 2, 3) 17 | if len(arr) != 1 { 18 | t.Errorf("Got %d rows, expected %d", len(arr), 1) 19 | } 20 | if len(arr[0]) != 2 { 21 | t.Errorf("Got %d cols, expected %d", len(arr[0]), 2) 22 | } 23 | if len(arr[0][0]) != 3 { 24 | t.Errorf("Got %d third, expected %d", len(arr), 3) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stdjava/optional.go: -------------------------------------------------------------------------------- 1 | package stdjava 2 | 3 | // Option formally represents a value that can be nil 4 | type Optional[T any] struct { 5 | value *T 6 | } 7 | 8 | // Some returns true if a value is present 9 | func (o Optional[T]) Some() bool { 10 | return o.value != nil 11 | } 12 | -------------------------------------------------------------------------------- /stdjava/shift_test.go: -------------------------------------------------------------------------------- 1 | package stdjava 2 | 3 | import "testing" 4 | 5 | // Examples from the JavaScript reference at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift 6 | 7 | func TestRightShiftPositive(t *testing.T) { 8 | if UnsignedRightShift(9, 2) != 2 { 9 | t.Errorf("Shifted 9 >>> 2. Expected 2 but got %d", UnsignedRightShift(9, 2)) 10 | } 11 | } 12 | 13 | func TestRightShiftNegative(t *testing.T) { 14 | if UnsignedRightShift(-9, 2) != 1073741821 { 15 | t.Errorf("Shifted -9 >>> 2. Expected 1073741821 but got %d", UnsignedRightShift(-9, 2)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /symbol/class_scope.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // ClassScope represents a single defined class, and the declarations in it 4 | type ClassScope struct { 5 | // The definition for the class defined within the class 6 | Class *Definition 7 | // Every class that is nested within the base class 8 | Subclasses []*ClassScope 9 | // Any normal and static fields associated with the class 10 | Fields []*Definition 11 | // Methods and constructors 12 | Methods []*Definition 13 | } 14 | 15 | // FindMethod searches through the immediate class's methods find a specific method 16 | func (cs *ClassScope) FindMethod() Finder { 17 | cm := classMethodFinder(*cs) 18 | return &cm 19 | } 20 | 21 | // FindField searches through the immediate class's fields to find a specific field 22 | func (cs *ClassScope) FindField() Finder { 23 | cm := classFieldFinder(*cs) 24 | return &cm 25 | } 26 | 27 | type classMethodFinder ClassScope 28 | 29 | func (cm *classMethodFinder) By(criteria func(d *Definition) bool) []*Definition { 30 | results := []*Definition{} 31 | for _, method := range cm.Methods { 32 | if criteria(method) { 33 | results = append(results, method) 34 | } 35 | } 36 | return results 37 | } 38 | 39 | func (cm *classMethodFinder) ByName(name string) []*Definition { 40 | return cm.By(func(d *Definition) bool { 41 | return d.Name == name 42 | }) 43 | } 44 | 45 | func (cm *classMethodFinder) ByOriginalName(originalName string) []*Definition { 46 | return cm.By(func(d *Definition) bool { 47 | return d.OriginalName == originalName 48 | }) 49 | } 50 | 51 | type classFieldFinder ClassScope 52 | 53 | func (cm *classFieldFinder) By(criteria func(d *Definition) bool) []*Definition { 54 | results := []*Definition{} 55 | for _, method := range cm.Fields { 56 | if criteria(method) { 57 | results = append(results, method) 58 | } 59 | } 60 | return results 61 | } 62 | 63 | func (cm *classFieldFinder) ByName(name string) []*Definition { 64 | return cm.By(func(d *Definition) bool { 65 | return d.Name == name 66 | }) 67 | } 68 | 69 | func (cm *classFieldFinder) ByOriginalName(originalName string) []*Definition { 70 | return cm.By(func(d *Definition) bool { 71 | return d.OriginalName == originalName 72 | }) 73 | } 74 | 75 | // FindMethodByDisplayName searches for a given method by its display name 76 | // If some ignored parameter types are specified as non-nil, it will skip over 77 | // any function that matches these ignored parameter types exactly 78 | func (cs *ClassScope) FindMethodByName(name string, ignoredParameterTypes []string) *Definition { 79 | return cs.findMethodWithComparison(func(method *Definition) bool { return method.OriginalName == name }, ignoredParameterTypes) 80 | } 81 | 82 | // FindMethodByDisplayName searches for a given method by its display name 83 | // If some ignored parameter types are specified as non-nil, it will skip over 84 | // any function that matches these ignored parameter types exactly 85 | func (cs *ClassScope) FindMethodByDisplayName(name string, ignoredParameterTypes []string) *Definition { 86 | return cs.findMethodWithComparison(func(method *Definition) bool { return method.Name == name }, ignoredParameterTypes) 87 | } 88 | 89 | func (cs *ClassScope) findMethodWithComparison(comparison func(method *Definition) bool, ignoredParameterTypes []string) *Definition { 90 | for _, method := range cs.Methods { 91 | if comparison(method) { 92 | // If no parameters were specified to ignore, then return the first match 93 | if ignoredParameterTypes == nil { 94 | return method 95 | } else if len(method.Parameters) != len(ignoredParameterTypes) { // Size of parameters were not equal, instantly not equal 96 | return method 97 | } 98 | 99 | // Check the remaining paramters one-by-one 100 | for index, parameter := range method.Parameters { 101 | if parameter.OriginalType != ignoredParameterTypes[index] { 102 | return method 103 | } 104 | } 105 | } 106 | } 107 | 108 | // Not found 109 | return nil 110 | } 111 | 112 | // FindClass searches through a class file and returns the definition for the 113 | // found class, or nil if none was found 114 | func (cs *ClassScope) FindClass(name string) *Definition { 115 | if cs.Class.OriginalName == name { 116 | return cs.Class 117 | } 118 | for _, subclass := range cs.Subclasses { 119 | class := subclass.FindClass(name) 120 | if class != nil { 121 | return class 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | // FindFieldByName searches for a field by its original name, and returns its definition 128 | // or nil if none was found 129 | func (cs *ClassScope) FindFieldByName(name string) *Definition { 130 | for _, field := range cs.Fields { 131 | if field.OriginalName == name { 132 | return field 133 | } 134 | } 135 | return nil 136 | } 137 | 138 | func (cs *ClassScope) FindFieldByDisplayName(name string) *Definition { 139 | for _, field := range cs.Fields { 140 | if field.Name == name { 141 | return field 142 | } 143 | } 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /symbol/definition.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // Definition represents the name and type of a single symbol 4 | type Definition struct { 5 | // The original Java name 6 | OriginalName string 7 | // The display name of the definition, may be different from the original name 8 | Name string 9 | // Original Java type of the object 10 | OriginalType string 11 | // Display type of the object 12 | Type string 13 | 14 | // If the definition is a constructor 15 | // This is used so that the definition handles its special naming and 16 | // type rules correctly 17 | Constructor bool 18 | // If the object is a function, it has parameters 19 | Parameters []*Definition 20 | // Children of the declaration, if the declaration is a scope 21 | Children []*Definition 22 | } 23 | 24 | // Rename changes the display name of a definition 25 | func (d *Definition) Rename(name string) { 26 | d.Name = name 27 | } 28 | 29 | // ParameterByName returns a parameter's definition, given its original name 30 | func (d *Definition) ParameterByName(name string) *Definition { 31 | for _, param := range d.Parameters { 32 | if param.OriginalName == name { 33 | return param 34 | } 35 | } 36 | return nil 37 | } 38 | 39 | // OriginalParameterTypes returns a list of the original types for all the parameters 40 | func (d *Definition) OriginalParameterTypes() []string { 41 | names := make([]string, len(d.Parameters)) 42 | for ind, param := range d.Parameters { 43 | names[ind] = param.OriginalType 44 | } 45 | return names 46 | } 47 | 48 | // FindVariable searches a definition's immediate children and parameters 49 | // to try and find a given variable by its original name 50 | func (d *Definition) FindVariable(name string) *Definition { 51 | for _, param := range d.Parameters { 52 | if param.OriginalName == name { 53 | return param 54 | } 55 | } 56 | for _, child := range d.Children { 57 | if child.OriginalName == name { 58 | return child 59 | } 60 | } 61 | return nil 62 | } 63 | 64 | func (d Definition) IsEmpty() bool { 65 | return d.OriginalName == "" && len(d.Children) == 0 66 | } 67 | -------------------------------------------------------------------------------- /symbol/file_scope.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // FileScope represents the scope in a single source file, that can contain one 4 | // or more source classes 5 | type FileScope struct { 6 | // The global package that the file is located in 7 | Package string 8 | // Every external package that is imported into the file 9 | // Formatted as map[ImportedType: full.package.path] 10 | Imports map[string]string 11 | // The base class that is in the file 12 | BaseClass *ClassScope 13 | } 14 | 15 | // FindClass searches through a file to find if a given class has been defined 16 | // at its root class, or within any of the subclasses 17 | func (fs *FileScope) FindClass(name string) *Definition { 18 | if def := fs.BaseClass.FindClass(name); def != nil { 19 | return def 20 | } 21 | for _, subclass := range fs.BaseClass.Subclasses { 22 | if def := subclass.FindClass(name); def != nil { 23 | return def 24 | } 25 | } 26 | return nil 27 | } 28 | 29 | // FindField searches through all of the classes in a file and determines if a 30 | // field exists 31 | func (cs *FileScope) FindField() Finder { 32 | cm := fileFieldFinder(*cs) 33 | return &cm 34 | } 35 | 36 | type fileFieldFinder FileScope 37 | 38 | func findFieldsInClass(class *ClassScope, criteria func(d *Definition) bool) []*Definition { 39 | defs := class.FindField().By(criteria) 40 | for _, subclass := range class.Subclasses { 41 | defs = append(defs, findFieldsInClass(subclass, criteria)...) 42 | } 43 | return defs 44 | } 45 | 46 | func (ff *fileFieldFinder) By(criteria func(d *Definition) bool) []*Definition { 47 | return findFieldsInClass(ff.BaseClass, criteria) 48 | } 49 | 50 | func (ff *fileFieldFinder) ByName(name string) []*Definition { 51 | return ff.By(func(d *Definition) bool { 52 | return d.Name == name 53 | }) 54 | } 55 | 56 | func (ff *fileFieldFinder) ByOriginalName(originalName string) []*Definition { 57 | return ff.By(func(d *Definition) bool { 58 | return d.OriginalName == originalName 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /symbol/find.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // Finder represents an object that can search through its contents for a given 4 | // list of definitions that match a certian criteria 5 | type Finder interface { 6 | By(criteria func(d *Definition) bool) []*Definition 7 | ByName(name string) []*Definition 8 | ByOriginalName(originalName string) []*Definition 9 | } 10 | -------------------------------------------------------------------------------- /symbol/globals.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | var ( 4 | // GlobalScope represents the global symbol table, and contains a mapping 5 | // between the package's path, and its symbols 6 | // 7 | // Example: 8 | // "net.java.math" -> Symbols { Vectors, Cos } 9 | GlobalScope = &GlobalSymbols{Packages: make(map[string]*PackageScope)} 10 | ) 11 | 12 | // AddSymbolsToPackage adds a given file's symbols to the global package scope 13 | func AddSymbolsToPackage(symbols *FileScope) { 14 | if _, exist := GlobalScope.Packages[symbols.Package]; !exist { 15 | GlobalScope.Packages[symbols.Package] = NewPackageScope() 16 | } 17 | GlobalScope.Packages[symbols.Package].Files[symbols.BaseClass.Class.Name] = symbols 18 | } 19 | 20 | // A GlobalSymbols represents a global view of all the packages in the parsed source 21 | type GlobalSymbols struct { 22 | // Every package's path associatedd with its definition 23 | Packages map[string]*PackageScope 24 | } 25 | 26 | func (gs *GlobalSymbols) String() string { 27 | result := "" 28 | for packageName := range gs.Packages { 29 | result += packageName + "\n" 30 | } 31 | return result 32 | } 33 | 34 | // FindPackage looks up a package's path in the global scope, and returns it 35 | func (gs *GlobalSymbols) FindPackage(name string) *PackageScope { 36 | return gs.Packages[name] 37 | } 38 | -------------------------------------------------------------------------------- /symbol/package_scope.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // PackageScope represents a single package, which can contain one or more files 4 | type PackageScope struct { 5 | // Maps the file's name to its definitions 6 | Files map[string]*FileScope 7 | } 8 | 9 | func NewPackageScope() *PackageScope { 10 | return &PackageScope{ 11 | Files: make(map[string]*FileScope), 12 | } 13 | } 14 | 15 | func (ps *PackageScope) ExcludeFile(excludedFileName string) *PackageScope { 16 | newScope := &PackageScope{Files: make(map[string]*FileScope)} 17 | for fileName, fileScope := range ps.Files { 18 | if fileName != excludedFileName { 19 | newScope.Files[fileName] = fileScope 20 | } 21 | } 22 | return newScope 23 | } 24 | 25 | func (ps *PackageScope) FindStaticField() Finder { 26 | pf := PackageFieldFinder(*ps) 27 | return &pf 28 | } 29 | 30 | type PackageFieldFinder PackageScope 31 | 32 | func (pf *PackageFieldFinder) By(criteria func(d *Definition) bool) []*Definition { 33 | results := []*Definition{} 34 | for _, file := range pf.Files { 35 | for _, field := range file.BaseClass.Fields { 36 | if criteria(field) { 37 | results = append(results, field) 38 | } 39 | } 40 | } 41 | return results 42 | } 43 | 44 | func (ps *PackageFieldFinder) ByName(name string) []*Definition { 45 | return ps.By(func(d *Definition) bool { 46 | return d.Name == name 47 | }) 48 | } 49 | 50 | func (ps *PackageFieldFinder) ByOriginalName(originalName string) []*Definition { 51 | return ps.By(func(d *Definition) bool { 52 | return d.Name == originalName 53 | }) 54 | } 55 | 56 | func (ps *PackageScope) AddSymbolsFromFile(symbols *FileScope) { 57 | ps.Files[symbols.BaseClass.Class.Name] = symbols 58 | } 59 | 60 | // FindClass searches for a class in the given package and returns a scope for it 61 | // the class may be the subclass of another class 62 | func (ps *PackageScope) FindClass(name string) *ClassScope { 63 | for _, fileScope := range ps.Files { 64 | if fileScope.BaseClass.Class.OriginalName == name { 65 | return fileScope.BaseClass 66 | } 67 | for _, subclass := range fileScope.BaseClass.Subclasses { 68 | class := subclass.FindClass(name) 69 | if class != nil { 70 | return fileScope.BaseClass 71 | } 72 | } 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /symbol/parsing.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | import ( 4 | "github.com/NickyBoy89/java2go/astutil" 5 | "github.com/NickyBoy89/java2go/nodeutil" 6 | sitter "github.com/smacker/go-tree-sitter" 7 | ) 8 | 9 | // ParseSymbols generates a symbol table for a single class file. 10 | func ParseSymbols(root *sitter.Node, source []byte) *FileScope { 11 | var filePackage string 12 | 13 | var baseClass *sitter.Node 14 | 15 | imports := make(map[string]string) 16 | for _, node := range nodeutil.NamedChildrenOf(root) { 17 | switch node.Type() { 18 | case "package_declaration": 19 | filePackage = node.NamedChild(0).Content(source) 20 | case "import_declaration": 21 | importedItem := node.NamedChild(0).ChildByFieldName("name").Content(source) 22 | importPath := node.NamedChild(0).ChildByFieldName("scope").Content(source) 23 | 24 | imports[importedItem] = importPath 25 | case "class_declaration", "interface_declaration", "enum_declaration", "annotation_type_declaration": 26 | baseClass = node 27 | } 28 | } 29 | 30 | return &FileScope{ 31 | Imports: imports, 32 | Package: filePackage, 33 | BaseClass: parseClassScope(baseClass, source), 34 | } 35 | } 36 | 37 | func parseClassScope(root *sitter.Node, source []byte) *ClassScope { 38 | var public bool 39 | // Rename the type based on the public/static rules 40 | if root.NamedChild(0).Type() == "modifiers" { 41 | for _, node := range nodeutil.UnnamedChildrenOf(root.NamedChild(0)) { 42 | if node.Type() == "public" { 43 | public = true 44 | } 45 | } 46 | } 47 | 48 | nodeutil.AssertTypeIs(root.ChildByFieldName("name"), "identifier") 49 | 50 | // Parse the main class in the file 51 | 52 | className := root.ChildByFieldName("name").Content(source) 53 | scope := &ClassScope{ 54 | Class: &Definition{ 55 | OriginalName: className, 56 | Name: HandleExportStatus(public, className), 57 | }, 58 | } 59 | 60 | // Parse the body of the class 61 | 62 | for _, node := range nodeutil.NamedChildrenOf(root.ChildByFieldName("body")) { 63 | 64 | switch node.Type() { 65 | case "field_declaration": 66 | var public bool 67 | // Rename the type based on the public/static rules 68 | if node.NamedChild(0).Type() == "modifiers" { 69 | for _, modifier := range nodeutil.UnnamedChildrenOf(node.NamedChild(0)) { 70 | if modifier.Type() == "public" { 71 | public = true 72 | } 73 | } 74 | } 75 | 76 | fieldNameNode := node.ChildByFieldName("declarator").ChildByFieldName("name") 77 | 78 | nodeutil.AssertTypeIs(fieldNameNode, "identifier") 79 | 80 | // TODO: Scoped type identifiers are in a format such as RemotePackage.ClassName 81 | // To handle this, we remove the RemotePackage part, and depend on the later 82 | // type resolution to figure things out 83 | 84 | // The node that the field's type comes from 85 | typeNode := node.ChildByFieldName("type") 86 | 87 | // If the field is being assigned to a value 88 | if typeNode.Type() == "scoped_type_identifier" { 89 | typeNode = typeNode.NamedChild(int(typeNode.NamedChildCount()) - 1) 90 | } 91 | 92 | // The converted name and type of the field 93 | fieldName := fieldNameNode.Content(source) 94 | fieldType := nodeToStr(astutil.ParseType(typeNode, source)) 95 | 96 | scope.Fields = append(scope.Fields, &Definition{ 97 | Name: HandleExportStatus(public, fieldName), 98 | OriginalName: fieldName, 99 | Type: fieldType, 100 | OriginalType: typeNode.Content(source), 101 | }) 102 | case "method_declaration", "constructor_declaration": 103 | var public bool 104 | // Rename the type based on the public/static rules 105 | if node.NamedChild(0).Type() == "modifiers" { 106 | for _, modifier := range nodeutil.UnnamedChildrenOf(node.NamedChild(0)) { 107 | if modifier.Type() == "public" { 108 | public = true 109 | } 110 | } 111 | } 112 | 113 | nodeutil.AssertTypeIs(node.ChildByFieldName("name"), "identifier") 114 | 115 | name := node.ChildByFieldName("name").Content(source) 116 | declaration := &Definition{ 117 | Name: HandleExportStatus(public, name), 118 | OriginalName: name, 119 | Parameters: []*Definition{}, 120 | } 121 | 122 | if node.Type() == "method_declaration" { 123 | declaration.Type = nodeToStr(astutil.ParseType(node.ChildByFieldName("type"), source)) 124 | declaration.OriginalType = node.ChildByFieldName("type").Content(source) 125 | } else { 126 | // A constructor declaration returns the type being constructed 127 | 128 | // Rename the constructor with "New" + name of type 129 | declaration.Rename(HandleExportStatus(public, "New") + name) 130 | declaration.Constructor = true 131 | 132 | // There is no original type, and the constructor returns the name of 133 | // the new type 134 | declaration.Type = name 135 | } 136 | 137 | // Parse the parameters 138 | 139 | for _, parameter := range nodeutil.NamedChildrenOf(node.ChildByFieldName("parameters")) { 140 | 141 | var paramName string 142 | var paramType *sitter.Node 143 | 144 | // If this is a spread parameter, then it will be in the format: 145 | // (type) (variable_declarator name: (name)) 146 | if parameter.Type() == "spread_parameter" { 147 | paramName = parameter.NamedChild(1).ChildByFieldName("name").Content(source) 148 | paramType = parameter.NamedChild(0) 149 | } else { 150 | paramName = parameter.ChildByFieldName("name").Content(source) 151 | paramType = parameter.ChildByFieldName("type") 152 | } 153 | 154 | declaration.Parameters = append(declaration.Parameters, &Definition{ 155 | Name: paramName, 156 | OriginalName: paramName, 157 | Type: nodeToStr(astutil.ParseType(paramType, source)), 158 | OriginalType: paramType.Content(source), 159 | }) 160 | } 161 | 162 | if node.ChildByFieldName("body") != nil { 163 | methodScope := parseScope(node.ChildByFieldName("body"), source) 164 | if !methodScope.IsEmpty() { 165 | declaration.Children = append(declaration.Children, methodScope.Children...) 166 | } 167 | } 168 | 169 | scope.Methods = append(scope.Methods, declaration) 170 | case "class_declaration", "interface_declaration", "enum_declaration": 171 | other := parseClassScope(node, source) 172 | // Any subclasses will be renamed to part of their parent class 173 | other.Class.Rename(scope.Class.Name + other.Class.Name) 174 | scope.Subclasses = append(scope.Subclasses, other) 175 | } 176 | } 177 | 178 | return scope 179 | } 180 | 181 | func parseScope(root *sitter.Node, source []byte) *Definition { 182 | def := &Definition{} 183 | for _, node := range nodeutil.NamedChildrenOf(root) { 184 | switch node.Type() { 185 | case "local_variable_declaration": 186 | /* 187 | name := nodeToStr(ParseExpr(node.ChildByFieldName("declarator").ChildByFieldName("name"), source, Ctx{})) 188 | def.Children = append(def.Children, &symbol.Definition{ 189 | OriginalName: name, 190 | OriginalType: node.ChildByFieldName("type").Content(source), 191 | Type: nodeToStr(ParseExpr(node.ChildByFieldName("type"), source, Ctx{})), 192 | Name: name, 193 | }) 194 | */ 195 | case "for_statement", "enhanced_for_statement", "while_statement", "if_statement": 196 | def.Children = append(def.Children, parseScope(node, source)) 197 | } 198 | } 199 | return def 200 | } 201 | -------------------------------------------------------------------------------- /symbol/parsing_helpers.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | import ( 4 | "bytes" 5 | "go/printer" 6 | "go/token" 7 | "unicode" 8 | ) 9 | 10 | // Uppercase uppercases the first character of the given string 11 | func Uppercase(name string) string { 12 | return string(unicode.ToUpper(rune(name[0]))) + name[1:] 13 | } 14 | 15 | // Lowercase lowercases the first character of the given string 16 | func Lowercase(name string) string { 17 | return string(unicode.ToLower(rune(name[0]))) + name[1:] 18 | } 19 | 20 | // HandleExportStatus is a convenience method for renaming methods that may be 21 | // either public or private, and need to be renamed 22 | func HandleExportStatus(exported bool, name string) string { 23 | if exported { 24 | return Uppercase(name) 25 | } 26 | return Lowercase(name) 27 | } 28 | 29 | // nodeToStr converts any AST node to its string representation 30 | func nodeToStr(node any) string { 31 | var s bytes.Buffer 32 | err := printer.Fprint(&s, token.NewFileSet(), node) 33 | if err != nil { 34 | panic(err) 35 | } 36 | return s.String() 37 | } 38 | 39 | func NodeToStr(node any) string { 40 | return nodeToStr(node) 41 | } 42 | -------------------------------------------------------------------------------- /symbol/symbols.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | import ( 4 | sitter "github.com/smacker/go-tree-sitter" 5 | ) 6 | 7 | // Go reserved keywords that are not Java keywords, and create invalid code 8 | var reservedKeywords = []string{"chan", "defer", "fallthrough", "func", "go", "map", "range", "select", "struct", "type"} 9 | 10 | // IsReserved tests if a given identifier conflicts with a Go reserved keyword 11 | func IsReserved(name string) bool { 12 | for _, keyword := range reservedKeywords { 13 | if keyword == name { 14 | return true 15 | } 16 | } 17 | return false 18 | } 19 | 20 | // TypeOfLiteral returns the corresponding type for a Java literal 21 | func TypeOfLiteral(node *sitter.Node, source []byte) string { 22 | var originalType string 23 | 24 | switch node.Type() { 25 | case "decimal_integer_literal": 26 | switch node.Content(source)[len(node.Content(source))-1] { 27 | case 'L': 28 | originalType = "long" 29 | default: 30 | originalType = "int" 31 | } 32 | case "hex_integer_literal": 33 | panic("here") 34 | case "decimal_floating_point_literal": 35 | switch node.Content(source)[len(node.Content(source))-1] { 36 | case 'D': 37 | originalType = "double" 38 | default: 39 | originalType = "float" 40 | } 41 | case "string_literal": 42 | originalType = "String" 43 | case "character_literal": 44 | originalType = "char" 45 | } 46 | 47 | return originalType 48 | } 49 | 50 | // ResolveDefinition resolves a given definition, given its scope in the file 51 | // It returns `true` on a successful resolution, or `false` otherwise 52 | // 53 | // Resolving a definition means that the type of the file is matched up with the type defined 54 | // in the local scope or otherwise 55 | func ResolveDefinition(definition *Definition, fileScope *FileScope) bool { 56 | // Look in the class scope first 57 | //if localClassDef := fileScope.FindClass().ByType(definition.Type); localClassDef != nil { 58 | if localClassDef := fileScope.BaseClass.FindClass(definition.Type); localClassDef != nil { 59 | // Every type in the local scope is a reference type, so prefix it with a pointer 60 | definition.Type = "*" + localClassDef.Name 61 | return true 62 | 63 | } else if globalDef, in := fileScope.Imports[definition.Type]; in { // Look through the imports 64 | // Find what package the type is in 65 | if packageDef := GlobalScope.FindPackage(globalDef); packageDef != nil { 66 | definition.Type = packageDef.FindClass(definition.Type).FindClass(definition.Type).Type 67 | } 68 | return true 69 | } 70 | 71 | // Unresolved 72 | return false 73 | } 74 | 75 | // ResolveChildren recursively resolves a definition and all of its children 76 | // It returns true if all definitions were resolved correctly, and false otherwise 77 | func ResolveChildren(definition *Definition, fileScope *FileScope) bool { 78 | result := ResolveDefinition(definition, fileScope) 79 | for _, child := range definition.Children { 80 | result = ResolveChildren(child, fileScope) && result 81 | } 82 | return result 83 | } 84 | -------------------------------------------------------------------------------- /testfiles/AnnotatedPet.java: -------------------------------------------------------------------------------- 1 | public class AnnotatedPet { 2 | 3 | @Nullable private String name; 4 | 5 | public void setName(@Nullable String name) { 6 | this.name = name; 7 | } 8 | 9 | @Nullable 10 | public String getName() { 11 | return this.name; 12 | } 13 | 14 | @Deprecated 15 | @Environment(EnvType.CLIENT) 16 | public String sayHello() { 17 | return "Hello World!"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testfiles/ClassWithFieldNamedType.java: -------------------------------------------------------------------------------- 1 | public class ClassWithFieldNamedType { 2 | private int value; 3 | // This right here is problematic, as "type" in Go 4 | // Happens to be a reserved keyword, and not usable in a struct field 5 | private String type; 6 | 7 | public int GetValue() { 8 | return this.value; 9 | } 10 | 11 | public String GetType() { 12 | return this.type; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /testfiles/Compass.java: -------------------------------------------------------------------------------- 1 | enum Compass { 2 | NORTH, 3 | SOUTH, 4 | EAST, 5 | WEST; 6 | 7 | public Compass() {} 8 | 9 | public static String hello() { 10 | return "Hello World"; 11 | } 12 | 13 | public static void main(String[] args) { 14 | for (Compass c : Compass.values()) { 15 | System.out.println(c); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testfiles/ConditionOrdering.java: -------------------------------------------------------------------------------- 1 | class ConditionOrdering { 2 | public static void main(String[] args) { 3 | int index = 10; 4 | 5 | // This tests a corner-case where the condition of the loop should 6 | // be parsed correctly 7 | 8 | for (int i = 0; i < index - 1; i++) { 9 | System.out.println(i); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /testfiles/EnhancedForLoop.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tests for correct behavior around enhanced for loops 3 | */ 4 | class EnhancedForLoop { 5 | public static void main(String[] args) { 6 | String[] words = {"this", "should", "be", "iterated", "over"}; 7 | 8 | for (String word : words) { 9 | System.out.println(word); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /testfiles/GenericLinkedList.java: -------------------------------------------------------------------------------- 1 | public class GenericLinkedList { 2 | int size; 3 | Node head; 4 | Node tail; 5 | 6 | public class Node { 7 | T val; 8 | Node next; 9 | 10 | Node(T data) { // Node inherits T from GenericLinkedList 11 | this.val = data; 12 | } 13 | } 14 | 15 | /** Construct an GenericLinkedList. */ 16 | public GenericLinkedList() {} 17 | 18 | /** 19 | * Return the number of elements in the GenericLinkedList. 20 | * 21 | * @return The number of elements in the GenericLinkedList. 22 | */ 23 | public int size() { 24 | return this.size; 25 | } 26 | 27 | /** 28 | * Add an element to the end of the GenericLinkedList. 29 | * 30 | * @param element The element to add. 31 | */ 32 | public void add(T element) { 33 | this.size++; 34 | Node newNode = new Node(element); 35 | if (this.head == null) { 36 | this.head = newNode; 37 | this.tail = newNode; 38 | return; 39 | } 40 | 41 | this.tail.next = newNode; 42 | this.tail = newNode; 43 | } 44 | 45 | /** 46 | * Get the element at the specified index. 47 | * 48 | *

This function assumes that the index argument is within range of the GenericLinkedList. 49 | * 50 | * @param index The index to get. 51 | * @return The element at the specified index. 52 | */ 53 | public T get(int index) { 54 | Node curNode = this.head; 55 | for (int i = 0; i < this.size; i++) { 56 | if (i == index) { 57 | return curNode.val; 58 | } 59 | curNode = curNode.next; 60 | } 61 | return null; 62 | } 63 | 64 | /** 65 | * Remove the element at the specified index. 66 | * 67 | *

This function assumes that the index argument is within range of the GenericLinkedList. 68 | * 69 | * @param index The index to remove. 70 | */ 71 | public void remove(int index) { 72 | Node curNode = this.head; 73 | this.size--; 74 | if (index == 0) { 75 | this.head = this.head.next; 76 | return; 77 | } 78 | 79 | for (int i = 0; i < index - 1; i++) { 80 | curNode = curNode.next; 81 | } 82 | 83 | // Tail 84 | if (curNode.next.next == null) { 85 | this.tail = curNode; 86 | curNode.next = null; 87 | } else { 88 | curNode.next = curNode.next.next; 89 | } 90 | } 91 | 92 | /** 93 | * Create a String representation of the GenericLinkedList. 94 | * 95 | * @return A String representation of the GenericLinkedList. 96 | */ 97 | public String toString() { 98 | String result = "{"; 99 | if (this.size() > 0) { 100 | result += this.get(0); 101 | } 102 | for (int i = 1; i < this.size; i++) { 103 | result += ", " + this.get(i); 104 | } 105 | result += "}"; 106 | return result; 107 | } 108 | 109 | /** 110 | * Check that an GenericLinkedList contains the same elements as an int array. 111 | * 112 | *

If the list and the array are not the same, throw an AssertionError. 113 | * 114 | * @param list The GenericLinkedList to check. 115 | * @param answer The expected answer, in the form of an int array. 116 | */ 117 | public static void assertArraysEqual(GenericLinkedList list, int[] answer) { 118 | if (list.size() != answer.length) { 119 | throw new AssertionError( 120 | "Expected list of length " + answer.length + " but got " + list.size()); 121 | } 122 | for (int i = 0; i < answer.length; i++) { 123 | if ((Integer) list.get(i) != answer[i]) { 124 | throw new AssertionError( 125 | "Expected " + answer[i] + " but got " + list.get(i) + " at index " + i); 126 | } 127 | } 128 | } 129 | 130 | /* 131 | * Test that the empty arraylist has size 0. 132 | */ 133 | public static void test1() { 134 | GenericLinkedList list = new GenericLinkedList<>(); 135 | int[] answer = new int[0]; 136 | assertArraysEqual(list, answer); 137 | } 138 | 139 | /* 140 | * Test insertion into an arraylist (without resizing). 141 | */ 142 | public static void test2() { 143 | GenericLinkedList list = new GenericLinkedList<>(); 144 | for (int i = 0; i < 3; i++) { 145 | list.add((Integer) i * i); 146 | } 147 | int[] answer = {0, 1, 4}; 148 | assertArraysEqual(list, answer); 149 | } 150 | 151 | /* 152 | * Test deletion from an arraylist without emptying it. 153 | */ 154 | public static void test3() { 155 | GenericLinkedList list = new GenericLinkedList<>(); 156 | for (int i = 0; i < 5; i++) { 157 | list.add(i * i); 158 | } 159 | list.remove(1); 160 | list.remove(2); 161 | int[] answer = {0, 4, 16}; 162 | GenericLinkedList.assertArraysEqual(list, answer); 163 | } 164 | 165 | /* 166 | * Test deletion from an arraylist and emptying it. 167 | */ 168 | public static void test4() { 169 | GenericLinkedList list = new GenericLinkedList<>(); 170 | for (int i = 0; i < 5; i++) { 171 | list.add(i * i); 172 | } 173 | 174 | list.remove(1); 175 | list.remove(2); 176 | 177 | // delete the final remaining numbers 178 | list.remove(0); 179 | list.remove(0); 180 | list.remove(0); 181 | int[] answer1 = {}; 182 | GenericLinkedList.assertArraysEqual(list, answer1); 183 | 184 | // check that there are no last-element issues 185 | for (int i = 0; i < 5; i++) { 186 | list.add(i * i); 187 | } 188 | list.remove(4); 189 | list.add(-1); 190 | int[] answer2 = {0, 1, 4, 9, -1}; 191 | GenericLinkedList.assertArraysEqual(list, answer2); 192 | } 193 | 194 | /* 195 | * Test insertion into an arraylist (with resizing). 196 | */ 197 | public static void test5() { 198 | GenericLinkedList list = new GenericLinkedList<>(); 199 | for (int i = 0; i < 12; i++) { 200 | list.add(i * i); 201 | } 202 | int[] answer = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121}; 203 | GenericLinkedList.assertArraysEqual(list, answer); 204 | } 205 | 206 | /** 207 | * Put the GenericLinkedList through some simple tests. 208 | * 209 | * @param args Ignored command line arguments. 210 | */ 211 | public static void main(String[] args) { 212 | test1(); 213 | test2(); 214 | test3(); 215 | test4(); 216 | test5(); 217 | 218 | System.out.println("pass"); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /testfiles/IncrementDecrement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This tests for the correct handling of pre and post increment expressions, 3 | * as well as correct handling for them as statements 4 | */ 5 | 6 | public class IncrementDecrement { 7 | public static void main(String[] args) { 8 | // Since the increment for this loop is a statement, it should not be 9 | // generated as a pre-increment statement, and should be translated to a 10 | // native go `i++` statement 11 | for (int i = 0; i < 10; ++i) { 12 | System.out.println(String.format("The value of the varable is: %d", i)); 13 | } 14 | 15 | int baseValue = 0; 16 | 17 | // This should be translated into a native go increment statement 18 | baseValue++; 19 | 20 | // This should be still translated into a native go increment statement 21 | ++baseValue; 22 | 23 | // Since the variable's value is `2`, and its value gets incremented after 24 | // it is evaluated, this should return `2`, and the variable should now 25 | // be `3` 26 | System.out.println("This should return 2"); 27 | System.out.println(processValue(baseValue++)); 28 | 29 | System.out.println("The value of the variable should be 3"); 30 | System.out.println(baseValue); 31 | 32 | // The opposite, since `baseValue` is `3`, this increments the variable 33 | // before it is evaluated, this should return `4`, and the value should 34 | // be `4` as well 35 | System.out.println("This should return 4"); 36 | System.out.println(processValue(++baseValue)); 37 | 38 | System.out.println("The value of the variable should be 4 as well"); 39 | System.out.println(baseValue); 40 | } 41 | 42 | public static int processValue(int value) { 43 | return value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /testfiles/InlineStatements.java: -------------------------------------------------------------------------------- 1 | class InlineStatements { 2 | public static void main(String[] args) { 3 | int number = 4; 4 | 5 | if (number % 2 == 1) { 6 | System.out.println("Number is odd!"); 7 | } 8 | 9 | // Test for inline if statements 10 | if (number % 2 == 0) System.out.println("Number is even!"); 11 | 12 | System.out.println("The number is: " + number); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /testfiles/IntLinkedList.java: -------------------------------------------------------------------------------- 1 | import java.lang.AssertionError; 2 | 3 | /* 4 | * Template for a singly- or doubly-linked list of ints. 5 | */ 6 | public class IntLinkedList { 7 | 8 | class Node { 9 | int data; 10 | Node next; 11 | 12 | Node(int data) { 13 | this.data = data; 14 | this.next = null; 15 | } 16 | } 17 | 18 | int size; 19 | Node head; 20 | Node tail; 21 | 22 | /** 23 | * Construct an IntLinkedList. 24 | */ 25 | public IntLinkedList() { 26 | this.head = null; 27 | this.tail = null; 28 | this.size = 0; 29 | } 30 | 31 | /** 32 | * Return the number of elements in the IntLinkedList. 33 | * 34 | * @return The number of elements in the IntLinkedList. 35 | */ 36 | public int size() { 37 | return this.size; 38 | } 39 | 40 | /** 41 | * Add an element to the end of the IntLinkedList. 42 | * 43 | * @param element The element to add. 44 | */ 45 | public void add(int element) { 46 | if (this.head == null) { // Empty list 47 | this.size++; 48 | this.head = new Node(element); 49 | this.tail = this.head; 50 | } else { 51 | this.size++; 52 | this.tail.next = new Node(element); 53 | this.tail = this.tail.next; 54 | } 55 | } 56 | 57 | /** 58 | * Get the element at the specified index. 59 | * 60 | * This function assumes that the index argument is within range of the IntLinkedList. 61 | * 62 | * @param index The index to get. 63 | * @return The element at the specified index. 64 | */ 65 | public int get(int index) { 66 | Node currentNode = this.head; 67 | 68 | for (int i = 1; i <= index; i++) { 69 | currentNode = currentNode.next; 70 | } 71 | return currentNode.data; 72 | } 73 | 74 | /** 75 | * Remove the element at the specified index. 76 | * 77 | * This function assumes that the index argument is within range of the IntLinkedList. 78 | * 79 | * @param index The index to remove. 80 | */ 81 | public void remove(int index) { 82 | Node currentNode = this.head; 83 | Node previousNode = this.head; 84 | 85 | if (index == 0) { // Special case for removing an element at the start of the list 86 | this.head = this.head.next; 87 | } 88 | 89 | for (int i = 1; i <= index; i++) { 90 | previousNode = currentNode; 91 | currentNode = previousNode.next; 92 | } 93 | 94 | if (currentNode.next == null) { // Special case for removing an element at the end of the list 95 | this.tail = previousNode; 96 | } else { 97 | previousNode.next = currentNode.next; 98 | } 99 | 100 | this.size--; 101 | } 102 | 103 | /** 104 | * Create a String representation of the IntLinkedList. 105 | * 106 | * @return A String representation of the IntLinkedList. 107 | */ 108 | public String toString() { 109 | String result = "{"; 110 | if (this.size() > 0) { 111 | result += this.get(0); 112 | } 113 | for (int i = 1; i < this.size; i++) { 114 | result += ", " + this.get(i); 115 | } 116 | result += "}"; 117 | return result; 118 | } 119 | 120 | /** 121 | * Check that an IntLinkedList contains the same elements as an int array. 122 | * 123 | * If the list and the array are not the same, throw an AssertionError. 124 | * 125 | * @param list The IntLinkedList to check. 126 | * @param answer The expected answer, in the form of an int array. 127 | */ 128 | public static void assertArraysEqual(IntLinkedList list, int[] answer) { 129 | if (list.size() != answer.length) { 130 | throw new AssertionError("Expected list of length " + answer.length + " but got " + list.size()); 131 | } 132 | for (int i = 0; i < answer.length; i++) { 133 | if (list.get(i) != answer[i]) { 134 | throw new AssertionError("Expected " + answer[i] + " but got " + list.get(i) + " at index " + i); 135 | } 136 | } 137 | } 138 | 139 | /* 140 | * Test that the empty arraylist has size 0. 141 | */ 142 | public static void test1() { 143 | IntLinkedList list = new IntLinkedList(); 144 | int[] answer = new int[0]; 145 | assertArraysEqual(list, answer); 146 | } 147 | 148 | /* 149 | * Test insertion into an arraylist (without resizing). 150 | */ 151 | public static void test2() { 152 | IntLinkedList list = new IntLinkedList(); 153 | for (int i = 0; i < 3; i++) { 154 | list.add(i * i); 155 | } 156 | int[] answer = {0, 1, 4}; 157 | assertArraysEqual(list, answer); 158 | } 159 | 160 | /* 161 | * Test deletion from an arraylist without emptying it. 162 | */ 163 | public static void test3() { 164 | IntLinkedList list = new IntLinkedList(); 165 | for (int i = 0; i < 5; i++) { 166 | list.add(i * i); 167 | } 168 | list.remove(1); 169 | list.remove(2); 170 | int[] answer = {0, 4, 16}; 171 | IntLinkedList.assertArraysEqual(list, answer); 172 | } 173 | 174 | /* 175 | * Test deletion from an arraylist and emptying it. 176 | */ 177 | public static void test4() { 178 | IntLinkedList list = new IntLinkedList(); 179 | for (int i = 0; i < 5; i++) { 180 | list.add(i * i); 181 | } 182 | 183 | list.remove(1); 184 | list.remove(2); 185 | 186 | // delete the final remaining numbers 187 | list.remove(0); 188 | list.remove(0); 189 | list.remove(0); 190 | int[] answer1 = {}; 191 | IntLinkedList.assertArraysEqual(list, answer1); 192 | 193 | // check that there are no last-element issues 194 | for (int i = 0; i < 5; i++) { 195 | list.add(i * i); 196 | } 197 | list.remove(4); 198 | list.add(-1); 199 | int[] answer2 = {0, 1, 4, 9, -1}; 200 | IntLinkedList.assertArraysEqual(list, answer2); 201 | } 202 | 203 | /* 204 | * Test insertion into an arraylist (with resizing). 205 | */ 206 | public static void test5() { 207 | IntLinkedList list = new IntLinkedList(); 208 | for (int i = 0; i < 12; i++) { 209 | list.add(i * i); 210 | } 211 | int[] answer = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121}; 212 | IntLinkedList.assertArraysEqual(list, answer); 213 | } 214 | 215 | /** 216 | * Put the IntLinkedList through some simple tests. 217 | * 218 | * @param args Ignored command line arguments. 219 | */ 220 | public static void main(String[] args) { 221 | test1(); 222 | test2(); 223 | test3(); 224 | test4(); 225 | test5(); 226 | 227 | System.out.println("pass"); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /testfiles/LinkedList.java: -------------------------------------------------------------------------------- 1 | public class LinkedList { 2 | private int size = 0; 3 | 4 | private ListNode head; 5 | private ListNode tail; 6 | 7 | LinkedList() { 8 | this.head = null; 9 | this.tail = null; 10 | } 11 | 12 | public void add(ListNode elem) { 13 | this.add(elem, this.size); 14 | } 15 | 16 | public void add(ListNode elem, int index) { 17 | this.size++; 18 | if (this.size == 0) { 19 | this.head = elem; 20 | this.tail = elem; 21 | return; 22 | } else if (index > this.size - 1) { 23 | throw new IndexOutOfBoundsException("Specified index is larger than length of LinkedList"); 24 | } 25 | 26 | if (index == this.size) { 27 | this.tail.next = elem; 28 | elem.prev = this.tail; 29 | this.tail = elem; 30 | return; 31 | } else if (index == 0) { 32 | this.head.prev = elem; 33 | elem.next = this.head; 34 | this.head = elem; 35 | return; 36 | } 37 | 38 | ListNode curNode = this.head; 39 | for (int i = 0; i < index; i++) { 40 | curNode = curNode.next; 41 | } 42 | 43 | curNode.next.prev = elem; 44 | elem.next = curNode.next; 45 | curNode.next = elem; 46 | elem.prev = curNode; 47 | } 48 | 49 | public int GetSize() { 50 | return this.size; 51 | } 52 | 53 | public String toString() { 54 | String result = ""; 55 | System.out.println(result); 56 | ListNode curNode = this.head; 57 | for (int i = 0; i < this.size; i++) { 58 | result += " " + curNode.value; 59 | curNode = curNode.next; 60 | } 61 | return result; 62 | } 63 | 64 | class ListNode { 65 | ListNode next = null; 66 | ListNode prev = null; 67 | int value; 68 | 69 | public ListNode(int val) { 70 | this.value = val; 71 | } 72 | } 73 | 74 | public static void main(String[] args) { 75 | LinkedList linkedList = new LinkedList(); 76 | 77 | int[] toAdd = {1, 2, 3, 4, 5, 6, 7, 8, 9}; 78 | for (int i = 0; i < toAdd.length; i++) { 79 | 80 | ListNode newNode = new ListNode(toAdd[i]); 81 | linkedList.add(new ListNode(toAdd[i])); 82 | } 83 | 84 | System.out.println(linkedList); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /testfiles/LiteralsAndNulls.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This class tests 3 | */ 4 | class LiteralsAndNulls { 5 | public static void main(String[] args) { 6 | // Float types 7 | float n1 = 1.0F; 8 | double n2 = 1.0D; 9 | 10 | // Integer types 11 | long n3 = 1L; 12 | int n4 = 1; 13 | 14 | // Make sure objects are declared explicitly when they are `null` 15 | String n5 = null; 16 | 17 | System.out.println(n5); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testfiles/MathUtilsStatic.java: -------------------------------------------------------------------------------- 1 | class MathUtilsWithStatic { 2 | 3 | public static final double PI = 3.14159; 4 | @Nullable public static final String NILSTRING = new String(); 5 | 6 | public static int Min(int x1, int x2) { 7 | return x1 < x2 ? x1 : x2; 8 | } 9 | 10 | public static double GetPi() { 11 | return PI; 12 | } 13 | 14 | static { 15 | int i = 10; 16 | for (int j = 0; j < i; j++) { 17 | System.out.println(j); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /testfiles/MultiDimensionalArrays.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | class MultiDimensionalArrays { 4 | public static void main(String[] args) { 5 | // This is technically a zero-size multi-dimensional 6 | // array, so we need make sure it doesn't fail 7 | int[] test = new int[] {1, 2, 3}; 8 | System.out.println(Arrays.toString(test)); 9 | 10 | int[] test1 = new int[2]; 11 | System.out.println(Arrays.toString(test1)); 12 | 13 | int[][] test2 = new int[2][3]; 14 | System.out.println(Arrays.deepToString(test2)); 15 | 16 | int[][][] test3 = new int[2][3][4]; 17 | System.out.println(Arrays.deepToString(test3)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testfiles/NameCollisions.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public class NameCollisions { 8 | 9 | Map fruitTypes; 10 | 11 | // `map` is a reserved keyword 12 | Map map; 13 | 14 | Map test = new HashMap<>(); 15 | 16 | // Since `type` is a reserved keyword in Go, this should fail 17 | public int getFruit(String type) { 18 | return this.fruitTypes.get(type); 19 | } 20 | 21 | // This is also a collision, with the keyword `range` 22 | private int[] range() { 23 | int[] values = new int[this.map.size()]; 24 | int ind = 0; 25 | for (int val : this.map.values()) { 26 | values[ind] = val; 27 | ind++; 28 | } 29 | return values; 30 | } 31 | 32 | public NameCollisions() { 33 | // Another collision, but a little more subtle 34 | this.map = new HashMap<>(); 35 | } 36 | 37 | public static void main(String[] args) { 38 | NameCollisions test = new NameCollisions(); 39 | 40 | System.out.println(test.map); 41 | 42 | // Even more collisions 43 | Map map = new HashMap<>(); 44 | map.put("Apple", 1); 45 | map.put("Lemon", 4); 46 | 47 | test.map = map; 48 | System.out.println(Arrays.toString(test.range())); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /testfiles/RandomTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This class tests for the correct conversion of the Java types to their Go 3 | * equivalents 4 | */ 5 | class RandomTypes { 6 | public static void main(String[] args) { 7 | /* 8 | * Some basic rules are as follows: 9 | * int -> int32 10 | * long -> int64 11 | * short -> int16 12 | * double -> float64 13 | * float -> float32 14 | * char -> rune 15 | * boolean -> bool 16 | * String - string 17 | */ 18 | 19 | // Integral types 20 | int intType = 0; 21 | long longType = 0; 22 | short shortType = 0; 23 | 24 | // Floating point types 25 | float floatType = 0.0; 26 | double doubleType = 0.0; 27 | 28 | // Other random types: 29 | char character = 'a'; 30 | boolean condition = false; 31 | 32 | // Specical cases 33 | String testString = "test"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /testfiles/ResolveTesting.java: -------------------------------------------------------------------------------- 1 | public class ResolveTesting { 2 | 3 | // This should be changed 4 | Node temp; 5 | 6 | private class Node { 7 | int value; 8 | 9 | public Node(int value) { 10 | this.value = value; 11 | } 12 | } 13 | 14 | // The parameter of this should change 15 | public void incrementNode(Node target) { 16 | target.value++; 17 | } 18 | 19 | public int square(int x1, int x2) { 20 | return x1 * x2; 21 | } 22 | 23 | public Node add(Node n1, Node n2) { 24 | Node temp = null; 25 | temp = new Node(n1.value + n2.value); 26 | return temp; 27 | } 28 | 29 | // The parameter and return type should change 30 | public Node duplicateNode(Node target) { 31 | return new Node(target.value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /testfiles/ScrambledForLoops.java: -------------------------------------------------------------------------------- 1 | class ScrambledForLoops { 2 | public static void main(String[] args) { 3 | // A completely normal for loop 4 | System.out.println("A completely normal for loop"); 5 | for (int i = 0; i < 3; i++) { 6 | System.out.println(i); 7 | } 8 | 9 | // Remove the pre-condition statement from the loop. This should not change 10 | // how the loop behaves 11 | int j = 0; 12 | System.out.println("Remove the pre-condition"); 13 | for (; j < 3; j++) { 14 | System.out.println(j); 15 | } 16 | 17 | // Remove the condition, this should not change behavior in this case 18 | System.out.println("Remove the statement"); 19 | for (int i = 0; ; i++) { 20 | if (i >= 3) { 21 | break; 22 | } 23 | System.out.println(i); 24 | } 25 | 26 | // Remove the post-expression 27 | System.out.println("Remove the post-expression"); 28 | for (int i = 0; i < 3; ) { 29 | System.out.println(i); 30 | i++; 31 | } 32 | 33 | System.out.println("Multiple declaration"); 34 | int e; 35 | int f; 36 | for (e = 1, f = 1; e < 3; e++) { 37 | System.out.println(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /testfiles/SelectorNewExpression.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This tests a specific case for `new` expressions, where they are specified 3 | * in a very specific way 4 | */ 5 | 6 | public class SelectorNewExpression { 7 | public static main(String[] args) { 8 | System.out.println("These should both be equal"); 9 | // This tests the difference between calling a new constructor in two different ways 10 | // This test originally came from the following example calls: 11 | // Fernflower Output: 12 | EditGameRulesScreen.this.new RuleCategoryWidget(); 13 | // CRF Output: 14 | new EditGameRulesScreen.RuleCategoryWidget(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testfiles/SimpleTest.java: -------------------------------------------------------------------------------- 1 | class SimpleTest { 2 | 3 | int value; 4 | public int value2; 5 | private int value3; 6 | 7 | SimpleTest(int value) { 8 | this.value = value; 9 | this.value2 = value + 1; 10 | this.value3 = value + 2; 11 | } 12 | 13 | public int getValue(int specified) { 14 | if (specified == 1) { 15 | return this.value; 16 | } else if (specified == 2) { 17 | return this.value2; 18 | } else { 19 | return this.value3; 20 | } 21 | } 22 | 23 | public static String hello() { 24 | return "Hello World!"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /testfiles/Test.java: -------------------------------------------------------------------------------- 1 | class Test{ 2 | int value; 3 | 4 | public Test(int val) { 5 | this.value = val; 6 | } 7 | 8 | public int GetValue() { 9 | return this.value; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /testfiles/TestInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * TestInterface tests for the correct handling of interfaces, with normal 3 | * methods, modifiers, and default methods 4 | */ 5 | public interface TestInterface { 6 | // Normal method 7 | boolean isTrue(); 8 | // Method with parameters 9 | float getAt(int index); 10 | // Default method 11 | default int getIndex() { 12 | return 0; 13 | } 14 | // Public method, name should be capitalized 15 | public void publicMethod(); 16 | // Annotated method 17 | @Unimportant 18 | int doesSomething(); 19 | } 20 | -------------------------------------------------------------------------------- /testfiles/VariableAssignments.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file tests for the correct handling of assignments, and most natably: 3 | * Assigning a variable to a value in a statement e.g `int variable = 1;`, 4 | * using a value as an expression e.g `int variable = this.value = 1;`, 5 | * and nested assignments `int variable = val1 = val2 = val3 = 3` 6 | */ 7 | public class VariableAssignments { 8 | public static void main(String[] args) { 9 | // Create and assign a variable 10 | int testVar = 0; 11 | 12 | // A variable with some modifier 13 | final int immutableVar = 1234; 14 | 15 | // Declare a variable that Go will guess wrong on, because the default 16 | // assumption for a floating point value is a float32 17 | double incorrectGuess = 1.0; 18 | 19 | // Assign a variable 20 | int variable; 21 | 22 | // Assign the variable to a new value in a statement 23 | variable = 1; 24 | 25 | // Set temp to the result of assigning 2 to `variable` 26 | int temp = variable = 2; 27 | // The temp variable should be 2 28 | System.out.println("This should be 2"); 29 | System.out.println(temp); 30 | 31 | int var1; 32 | int var2; 33 | int var3; 34 | int var4; 35 | 36 | System.out.println(var1 = var2 = var3 = var4 = 10); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /testfiles/extendsandimplements.java: -------------------------------------------------------------------------------- 1 | public class Cat extends Animal implements Pet { 2 | 3 | @Override 4 | public void pat() { 5 | System.out.println("The cat purrs"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /testfiles/output/IntLinkedList.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Node struct { 8 | data int 9 | next *Node 10 | } 11 | 12 | func NewNode(data int) *Node { 13 | return &Node{ 14 | data: data, 15 | next: nil, 16 | } 17 | } 18 | 19 | type IntLinkedList struct { 20 | size int 21 | head *Node 22 | tail *Node 23 | } 24 | 25 | func NewIntLinkedList() *IntLinkedList { 26 | return &IntLinkedList{ 27 | head: nil, 28 | tail: nil, 29 | size: 0, 30 | } 31 | } 32 | 33 | func (l IntLinkedList) Size() int { 34 | return l.size 35 | } 36 | 37 | func (l *IntLinkedList) Add(element int) { 38 | if l.head == nil { 39 | l.size++ 40 | l.head = NewNode(element) 41 | l.tail = l.head 42 | } else { 43 | l.size++ 44 | l.tail.next = NewNode(element) 45 | l.tail = l.tail.next 46 | } 47 | } 48 | 49 | func (l IntLinkedList) Get(index int) int { 50 | currentNode := l.head 51 | 52 | for i := 1; i <= index; i++ { 53 | currentNode = currentNode.next 54 | } 55 | return currentNode.data 56 | } 57 | 58 | func (l *IntLinkedList) Remove(index int) { 59 | currentNode := l.head 60 | previousNode := l.head 61 | 62 | if index == 0 { 63 | l.head = l.head.next 64 | } 65 | 66 | for i := 1; i <= index; i++ { 67 | previousNode = currentNode 68 | currentNode = previousNode.next 69 | } 70 | 71 | if currentNode.next == nil { 72 | l.tail = previousNode 73 | } else { 74 | previousNode.next = currentNode.next 75 | } 76 | 77 | l.size-- 78 | } 79 | 80 | func (l IntLinkedList) String() string { 81 | result := "{" 82 | if l.Size() > 0 { 83 | result += string(l.Get(0)) 84 | } 85 | for i := 1; i < l.size; i++ { 86 | result += ", " + string(l.Get(i)) 87 | } 88 | result += "}" 89 | return result 90 | } 91 | 92 | func AssertArraysEqual(list *IntLinkedList, answer []int) { 93 | if list.Size() != len(answer) { 94 | panic("Expected list of length " + string(len(answer)) + " but got " + string(list.Size())) 95 | } 96 | for i := 0; i < len(answer); i++ { 97 | if list.Get(i) != answer[i] { 98 | panic("Expected " + string(answer[i]) + " but got " + string(list.Get(i)) + " at index " + string(i)) 99 | } 100 | } 101 | } 102 | 103 | func Test1() { 104 | list := NewIntLinkedList() 105 | answer := []int{} 106 | AssertArraysEqual(list, answer) 107 | } 108 | 109 | func Test2() { 110 | list := NewIntLinkedList() 111 | for i := 0; i < 3; i++ { 112 | list.Add(i * i) 113 | } 114 | answer := []int{0, 1, 4} 115 | AssertArraysEqual(list, answer) 116 | } 117 | 118 | func Test3() { 119 | list := NewIntLinkedList() 120 | for i := 0; i < 5; i++ { 121 | list.Add(i * i) 122 | } 123 | list.Remove(1) 124 | list.Remove(2) 125 | answer := []int{0, 4, 16} 126 | AssertArraysEqual(list, answer) 127 | } 128 | 129 | func Test4() { 130 | list := NewIntLinkedList() 131 | for i := 0; i < 5; i++ { 132 | list.Add(i * i) 133 | } 134 | 135 | list.Remove(1) 136 | list.Remove(2) 137 | 138 | list.Remove(0) 139 | list.Remove(0) 140 | list.Remove(0) 141 | answer1 := []int{} 142 | AssertArraysEqual(list, answer1) 143 | 144 | for i := 0; i < 5; i++ { 145 | list.Add(i * i) 146 | } 147 | list.Remove(4) 148 | list.Add(-1) 149 | answer2 := []int{0, 1, 4, 9, -1} 150 | AssertArraysEqual(list, answer2) 151 | } 152 | 153 | func Test5() { 154 | list := NewIntLinkedList(); 155 | for i := 0; i < 12; i++ { 156 | list.Add(i * i) 157 | } 158 | answer := []int{0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121} 159 | AssertArraysEqual(list, answer) 160 | } 161 | 162 | func main() { 163 | Test1() 164 | Test2() 165 | Test3() 166 | Test4() 167 | Test5() 168 | 169 | fmt.Println("pass") 170 | } 171 | -------------------------------------------------------------------------------- /testfiles/output/Test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Test struct { 4 | value int 5 | } 6 | 7 | func NewTest(val int) *Test { 8 | tt := new(Test) 9 | tt.value = val 10 | return tt 11 | } 12 | func (tt *Test) GetValue() int { 13 | return tt.value 14 | } 15 | -------------------------------------------------------------------------------- /testfiles/output/class-with-type-field.go: -------------------------------------------------------------------------------- 1 | package testsnippets 2 | 3 | type TestClass struct { 4 | value int 5 | testClassType string 6 | } 7 | 8 | func (tc TestClass) GetValue() int { 9 | return tc.value 10 | } 11 | 12 | func (tc TestClass) GetType() string { 13 | return tc.testClassType 14 | } 15 | -------------------------------------------------------------------------------- /testfiles/output/simple.go: -------------------------------------------------------------------------------- 1 | type Test struct { 2 | value int 3 | Value2 int 4 | value3 int 5 | } 6 | 7 | func NewTest(value int) *Test { 8 | test := new(Test) 9 | 10 | test.Value = value 11 | test.Value2 = value + 1 12 | test.value3 = value + 2 13 | 14 | return test 15 | } 16 | 17 | func (t Test) GetValue(specified int) int { 18 | if (specified == 1) { 19 | return this.value 20 | } else if (specified == 2) { 21 | return this.value2 22 | } else { 23 | return this.value3 24 | } 25 | } 26 | 27 | func Hello() string { 28 | return "Hello World!" 29 | } 30 | -------------------------------------------------------------------------------- /testfiles/typechecks/MethodConstructorDeclaration.java: -------------------------------------------------------------------------------- 1 | class MethodConstructorDeclaration { 2 | 3 | // A method that only has a return type 4 | public static String sayHello() { 5 | return "Hello World"; 6 | } 7 | 8 | // One parameter 9 | public int squared(int n) { 10 | return n * n; 11 | } 12 | 13 | // Constructor 14 | public MethodConstructorDeclaration(double someNum) {} 15 | } 16 | -------------------------------------------------------------------------------- /testfiles/typechecks/SimpleDeclaration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SimpleDeclaration tests for correct handling of local variable declarations 3 | */ 4 | class SimpleDeclaration { 5 | public static void main(String[] args) { 6 | int variable = 42; 7 | System.out.println(variable); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /testfiles/typechecks/UntypedMap.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | import java.util.Map; 3 | 4 | /* 5 | * Tests for proper type inference in an untyped object 6 | */ 7 | public class UntypedMap { 8 | 9 | // This map has no type information supplied with it 10 | Map fruits; 11 | 12 | // This method supplies both correct types for both the key and value of the map 13 | public int getCount(String key) { 14 | return (int) this.fruits.get(key); 15 | } 16 | 17 | public static void main(String[] args) { 18 | UntypedMap storage = new UntypedMap(); 19 | storage.fruits = new HashMap(); 20 | storage.fruits.put("Apple", 1); 21 | storage.fruits.put("Orange", 3); 22 | System.out.println(storage.getCount("Apple")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tree_sitter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | 7 | "github.com/NickyBoy89/java2go/astutil" 8 | "github.com/NickyBoy89/java2go/nodeutil" 9 | "github.com/NickyBoy89/java2go/symbol" 10 | log "github.com/sirupsen/logrus" 11 | sitter "github.com/smacker/go-tree-sitter" 12 | ) 13 | 14 | // Inspect is a function for debugging that prints out every named child of a 15 | // given node and the source code for that child 16 | func Inspect(node *sitter.Node, source []byte) { 17 | for _, c := range nodeutil.NamedChildrenOf(node) { 18 | fmt.Println(c, c.Content(source)) 19 | } 20 | } 21 | 22 | // CapitalizeIdent capitalizes the first letter of a `*ast.Ident` to mark the 23 | // result as a public method or field 24 | func CapitalizeIdent(in *ast.Ident) *ast.Ident { 25 | return &ast.Ident{Name: symbol.Uppercase(in.Name)} 26 | } 27 | 28 | // LowercaseIdent lowercases the first letter of a `*ast.Ident` to mark the 29 | // result as a private method or field 30 | func LowercaseIdent(in *ast.Ident) *ast.Ident { 31 | return &ast.Ident{Name: symbol.Lowercase(in.Name)} 32 | } 33 | 34 | // A Ctx is all the context that is needed to parse a single source file 35 | type Ctx struct { 36 | // Used to generate the names of all the methods, as well as the names 37 | // of the constructors 38 | className string 39 | 40 | // Symbols for the current file being parsed 41 | currentFile *symbol.FileScope 42 | currentClass *symbol.ClassScope 43 | 44 | // The symbols of the current 45 | localScope *symbol.Definition 46 | 47 | // Used when generating arrays, because in Java, these are defined as 48 | // arrType[] varName = {item, item, item}, and no class name data is defined 49 | // Can either be of type `*ast.Ident` or `*ast.StarExpr` 50 | lastType ast.Expr 51 | } 52 | 53 | // Clone performs a shallow copy on a `Ctx`, returning a new Ctx with its pointers 54 | // pointing at the same things as the previous Ctx 55 | func (c Ctx) Clone() Ctx { 56 | return Ctx{ 57 | className: c.className, 58 | currentFile: c.currentFile, 59 | currentClass: c.currentClass, 60 | localScope: c.localScope, 61 | lastType: c.lastType, 62 | } 63 | } 64 | 65 | // ParseNode parses a given tree-sitter node and returns the ast representation 66 | // 67 | // This function is called when the node being parsed might not be a direct 68 | // expression or statement, as those are parsed with `ParseExpr` and `ParseStmt` 69 | // respectively 70 | func ParseNode(node *sitter.Node, source []byte, ctx Ctx) interface{} { 71 | switch node.Type() { 72 | case "ERROR": 73 | log.WithFields(log.Fields{ 74 | "parsed": node.Content(source), 75 | "className": ctx.className, 76 | }).Warn("Error parsing generic node") 77 | return &ast.BadStmt{} 78 | case "program": 79 | // A program contains all the source code, in this case, one `class_declaration` 80 | program := &ast.File{ 81 | Name: &ast.Ident{Name: "main"}, 82 | } 83 | 84 | for _, c := range nodeutil.NamedChildrenOf(node) { 85 | switch c.Type() { 86 | case "package_declaration": 87 | program.Name = &ast.Ident{Name: c.NamedChild(0).NamedChild(int(c.NamedChild(0).NamedChildCount()) - 1).Content(source)} 88 | case "class_declaration", "interface_declaration": 89 | program.Decls = ParseDecls(c, source, ctx) 90 | case "import_declaration": 91 | program.Imports = append(program.Imports, ParseNode(c, source, ctx).(*ast.ImportSpec)) 92 | } 93 | } 94 | return program 95 | case "field_declaration": 96 | var public bool 97 | 98 | if node.NamedChild(0).Type() == "modifiers" { 99 | for _, modifier := range nodeutil.UnnamedChildrenOf(node.NamedChild(0)) { 100 | if modifier.Type() == "public" { 101 | public = true 102 | } 103 | } 104 | } 105 | 106 | fieldType := ParseExpr(node.ChildByFieldName("type"), source, ctx) 107 | fieldName := ParseExpr(node.ChildByFieldName("declarator").ChildByFieldName("name"), source, ctx).(*ast.Ident) 108 | fieldName.Name = symbol.HandleExportStatus(public, fieldName.Name) 109 | 110 | // If the field is assigned to a value (ex: int field = 1) 111 | fieldAssignmentNode := node.ChildByFieldName("declarator").ChildByFieldName("value") 112 | if fieldAssignmentNode != nil { 113 | return &ast.ValueSpec{ 114 | Names: []*ast.Ident{fieldName}, 115 | Type: fieldType, 116 | Values: []ast.Expr{ 117 | ParseExpr(fieldAssignmentNode, source, ctx), 118 | }, 119 | } 120 | } 121 | 122 | return &ast.Field{ 123 | Names: []*ast.Ident{fieldName}, 124 | Type: fieldType, 125 | } 126 | case "import_declaration": 127 | return &ast.ImportSpec{Name: ParseExpr(node.NamedChild(0), source, ctx).(*ast.Ident)} 128 | case "method_declaration": 129 | comments := []*ast.Comment{} 130 | 131 | if node.NamedChild(0).Type() == "modifiers" { 132 | for _, modifier := range nodeutil.UnnamedChildrenOf(node.NamedChild(0)) { 133 | switch modifier.Type() { 134 | case "marker_annotation", "annotation": 135 | comments = append(comments, &ast.Comment{Text: "//" + modifier.Content(source)}) 136 | if _, in := excludedAnnotations[modifier.Content(source)]; in { 137 | // If this entire method is ignored, we return an empty field, which 138 | // is handled by the logic that parses a class file 139 | return &ast.Field{} 140 | } 141 | } 142 | } 143 | } 144 | 145 | parameters := &ast.FieldList{} 146 | 147 | for _, param := range nodeutil.NamedChildrenOf(node.ChildByFieldName("parameters")) { 148 | parameters.List = append(parameters.List, ParseNode(param, source, ctx).(*ast.Field)) 149 | } 150 | 151 | methodName := node.ChildByFieldName("name").Content(source) 152 | methodParameters := node.ChildByFieldName("parameters") 153 | 154 | comparison := func(d *symbol.Definition) bool { 155 | // The names must match 156 | if methodName != d.OriginalName { 157 | return false 158 | } 159 | 160 | // Size of parameters must match 161 | if int(methodParameters.NamedChildCount()) != len(d.Parameters) { 162 | return false 163 | } 164 | 165 | // Go through the types and check to see if they differ 166 | for index, param := range nodeutil.NamedChildrenOf(methodParameters) { 167 | var paramType string 168 | if param.Type() == "spread_parameter" { 169 | paramType = param.NamedChild(0).Content(source) 170 | } else { 171 | paramType = param.ChildByFieldName("type").Content(source) 172 | } 173 | if paramType != d.Parameters[index].OriginalType { 174 | return false 175 | } 176 | } 177 | 178 | return true 179 | } 180 | 181 | def := ctx.currentClass.FindMethod().By(comparison)[0] 182 | 183 | return &ast.Field{ 184 | Doc: &ast.CommentGroup{List: comments}, 185 | Names: []*ast.Ident{&ast.Ident{Name: def.Name}}, 186 | Type: &ast.FuncType{ 187 | Params: parameters, 188 | Results: &ast.FieldList{List: []*ast.Field{ 189 | &ast.Field{ 190 | Type: &ast.Ident{Name: def.Type}, 191 | }, 192 | }, 193 | }, 194 | }, 195 | } 196 | case "try_with_resources_statement": 197 | // Ignore try with resources statements as well 198 | // NOTE: This will also ignore the catch clause 199 | stmts := []ast.Stmt{ParseStmt(node.NamedChild(0), source, ctx)} 200 | return append(stmts, ParseStmt(node.NamedChild(1), source, ctx).(*ast.BlockStmt).List...) 201 | case "try_statement": 202 | // We ignore try statements 203 | return ParseStmt(node.NamedChild(0), source, ctx).(*ast.BlockStmt).List 204 | case "synchronized_statement": 205 | // A synchronized statement contains the variable to be synchronized, as 206 | // well as the block 207 | 208 | // Ignore the sychronized statement 209 | return ParseStmt(node.NamedChild(1), source, ctx).(*ast.BlockStmt).List 210 | case "switch_label": 211 | if node.NamedChildCount() > 0 { 212 | return &ast.CaseClause{ 213 | List: []ast.Expr{ParseExpr(node.NamedChild(0), source, ctx)}, 214 | } 215 | } 216 | return &ast.CaseClause{} 217 | case "argument_list": 218 | args := []ast.Expr{} 219 | for _, c := range nodeutil.NamedChildrenOf(node) { 220 | args = append(args, ParseExpr(c, source, ctx)) 221 | } 222 | return args 223 | 224 | case "formal_parameters": 225 | params := &ast.FieldList{} 226 | for _, param := range nodeutil.NamedChildrenOf(node) { 227 | params.List = append(params.List, ParseNode(param, source, ctx).(*ast.Field)) 228 | } 229 | return params 230 | case "formal_parameter": 231 | if ctx.localScope != nil { 232 | paramDef := ctx.localScope.ParameterByName(node.ChildByFieldName("name").Content(source)) 233 | if paramDef == nil { 234 | paramDef = &symbol.Definition{ 235 | Name: node.ChildByFieldName("name").Content(source), 236 | Type: node.ChildByFieldName("type").Content(source), 237 | } 238 | } 239 | return &ast.Field{ 240 | Names: []*ast.Ident{&ast.Ident{Name: paramDef.Name}}, 241 | Type: &ast.Ident{Name: paramDef.Type}, 242 | } 243 | } 244 | return &ast.Field{ 245 | Names: []*ast.Ident{ParseExpr(node.ChildByFieldName("name"), source, ctx).(*ast.Ident)}, 246 | Type: astutil.ParseType(node.ChildByFieldName("type"), source), 247 | } 248 | case "spread_parameter": 249 | // The spread paramater takes a list and separates it into multiple elements 250 | // Ex: addElements([]int elements...) 251 | 252 | spreadType := node.NamedChild(0) 253 | spreadDeclarator := node.NamedChild(1) 254 | 255 | return &ast.Field{ 256 | Names: []*ast.Ident{ParseExpr(spreadDeclarator.ChildByFieldName("name"), source, ctx).(*ast.Ident)}, 257 | Type: &ast.Ellipsis{ 258 | Elt: astutil.ParseType(spreadType, source), 259 | }, 260 | } 261 | case "inferred_parameters": 262 | params := &ast.FieldList{} 263 | for _, param := range nodeutil.NamedChildrenOf(node) { 264 | params.List = append(params.List, &ast.Field{ 265 | Names: []*ast.Ident{ParseExpr(param, source, ctx).(*ast.Ident)}, 266 | // When we're not sure what parameters to infer, set them as interface 267 | // values to avoid a panic 268 | Type: &ast.Ident{Name: "interface{}"}, 269 | }) 270 | } 271 | return params 272 | case "comment": // Ignore comments 273 | return nil 274 | } 275 | panic(fmt.Sprintf("Unknown node type: %v", node.Type())) 276 | } 277 | -------------------------------------------------------------------------------- /typecheck_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "reflect" 7 | "testing" 8 | 9 | sitter "github.com/smacker/go-tree-sitter" 10 | "github.com/smacker/go-tree-sitter/java" 11 | ) 12 | 13 | func loadFile(fileName string) ([]byte, *sitter.Tree) { 14 | parser := sitter.NewParser() 15 | parser.SetLanguage(java.GetLanguage()) 16 | 17 | source, err := os.ReadFile(fileName) 18 | if err != nil { 19 | panic(err) 20 | } 21 | tree, err := parser.ParseCtx(context.Background(), nil, source) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return source, tree 26 | } 27 | 28 | func TestSimpleDeclaration(t *testing.T) { 29 | source, tree := loadFile("testfiles/typechecks/SimpleDeclaration.java") 30 | 31 | expected := TypeInformation{ 32 | types: map[string]string{ 33 | "main": "", 34 | "args": "[]string", 35 | "variable": "int32", 36 | }, 37 | } 38 | 39 | info, err := ExtractTypeInformation(tree.RootNode(), source) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | if !reflect.DeepEqual(info, expected) { 44 | t.Errorf("Actual: %v did not meet expected: %v", info, expected) 45 | } 46 | } 47 | 48 | func TestMethodDeclaration(t *testing.T) { 49 | source, tree := loadFile("testfiles/typechecks/MethodConstructorDeclaration.java") 50 | 51 | expected := TypeInformation{ 52 | types: map[string]string{ 53 | "sayHello": "string", 54 | "squared": "int32", 55 | "n": "int32", 56 | "someNum": "float64", 57 | }, 58 | } 59 | 60 | info, err := ExtractTypeInformation(tree.RootNode(), source) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | if !reflect.DeepEqual(info, expected) { 65 | t.Errorf("Actual: %v did not meet expected: %v", info, expected) 66 | } 67 | } 68 | --------------------------------------------------------------------------------