contrib/jwrapper: use JavaTypeConverter as a singleton
[nit.git] / contrib / jwrapper / src / jwrapper.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
4 # Copyright 2015 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # jwrapper wraps Java classes in extern Nit classes
19 #
20 # This tool takes class file, Jar archives and javap files as input,
21 # and it outputs a Nit source file.
22 # For further details on installation and usage, refer to the README file.
23 #
24 # Here's an overview of the project design :
25 # * Grammar and lexer : `grammar/javap.sablecc`
26 # * The `javap_visitor` implements the visitor that extracts data from the AST
27 # * The `code_generator` takes these data and converts it to Nit code via the `jtype_converter` module and generate the output Nit file.
28 # * The `model` contains data structures used to represent the data
29 # * The `jwrapper` module implements the user interface
30 module jwrapper
31
32 import opts
33 import performance_analysis
34
35 import javap_test_parser
36 import code_generator
37 import javap_visitor
38
39 var opts = new OptionContext
40
41 var opt_unknown = new OptionEnum(["comment", "stub", "ignore"], "How to deal with unknown types", 0, "-u")
42 var opt_verbose = new OptionCount("Verbosity", "-v")
43 var opt_output = new OptionString("Output file", "-o")
44 var opt_help = new OptionBool("Show this help message", "-h", "--help")
45
46 opts.add_option(opt_output, opt_unknown, opt_verbose, opt_help)
47 opts.parse args
48
49 if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
50 print """
51 Usage: jwrapper [options] file [other_file [...]]
52 Input files: bytecode Java class (.class), Jar archive (.jar) or javap output (.javap)
53 Options:"""
54 opts.usage
55
56 if opt_help.value then exit 0
57 exit 1
58 end
59
60 var out_file = opt_output.value
61 if out_file == null then out_file = "out.nit"
62
63 if not "javap".program_is_in_path then
64 print "Error: 'javap' must be in PATH"
65 exit 1
66 end
67
68 # List of bytecode Java classes and javap output files
69 var class_files = new Array[String]
70 var javap_files = new Array[String]
71 var clock = new Clock
72
73 # Sort through input files passed as arguments
74 for input in opts.rest do
75 var ext = input.file_extension
76 if ext == "class" then
77 class_files.add input
78 else if ext == "javap" then
79 javap_files.add input
80 else if ext == "jar" then
81 var out_dir = "tmp"
82 clock.lapse
83
84 if opt_verbose.value > 0 then print "# Extracting {input}"
85
86 # Extract all files
87 var cmd = "cd {out_dir}; jar -xf {input}"
88 var status = system(cmd)
89 if status != 0 then
90 print_error "Warning: Failed to extract Jar archive '{input}'"
91 continue
92 end
93
94 # List .class files
95 var javap = new ProcessReader("jar", "-tf", input)
96 var output = javap.read_all
97 javap.wait
98 for path in output.split("\n") do
99 if path.file_extension == "class" then
100 class_files.add out_dir / path
101 end
102 end
103 javap.close
104
105 sys.perfs["jar extract"].add clock.lapse
106 else
107 print_error "Warning: Unsupported file extension for input file '{input}'"
108 end
109 end
110
111 if class_files.is_empty and javap_files.is_empty then
112 print_error "Error: No valid input files, quitting"
113 exit 1
114 end
115
116 var model = new JavaModel
117 var converter = new JavaTypeConverter
118
119 # Concatenated javap output for all the target files
120 var javap_output = ""
121
122 # Parse and analyze all the classes at once
123 if class_files.not_empty then
124
125 if opt_verbose.value > 0 then print "# Running javap on {class_files.length} Java classes"
126 clock.lapse
127
128 # Run javap of the class file
129 class_files.unshift "-public"
130 var javap = new ProcessReader("javap", class_files...)
131 javap_output += javap.read_all
132 javap.wait
133 javap.close
134 var status = javap.status
135
136 if status != 0 then
137 print_error "Warning: javap failed to parse all class files, is it a valid class file?"
138 exit 1
139 end
140
141 sys.perfs["javap"].add clock.lapse
142 end
143
144 # Concatenate the preprocessed javap outputs
145 for class_file in javap_files do
146 if opt_verbose.value > 0 then print "# Using the preprocessed file {class_file}"
147
148 var ext = class_file.file_extension
149 assert ext == "javap"
150
151 javap_output += class_file.to_path.read_all
152 end
153
154 if opt_verbose.value > 0 then print "# Parsing javap output"
155 if opt_verbose.value > 1 then javap_output.write_to_file "tests/javap.javap"
156
157 # Lexer
158 var lexer = new Lexer_javap(javap_output)
159 var parser = new Parser_javap
160 var tokens = lexer.lex
161 parser.tokens.add_all tokens
162 sys.perfs["core lexer"].add clock.lapse
163
164 # Parser
165 var root_node = parser.parse
166 if root_node isa NError then
167 print_error "Warning: Parsing failed with {root_node.message}:{root_node.position or else ""}"
168 exit 1
169 end
170 sys.perfs["core parser"].add clock.lapse
171
172 # Build model
173 if opt_verbose.value > 0 then print "# Building model"
174 assert root_node isa NStart
175 var visitor = new JavaVisitor(model)
176 visitor.enter_visit root_node
177 sys.perfs["core model"].add clock.lapse
178
179 if opt_verbose.value > 0 then print "# Generating Nit code"
180
181 var use_comment = opt_unknown.value == 0
182 var use_stub = opt_unknown.value == 1
183 var generator = new CodeGenerator(out_file, model, use_comment, use_stub)
184 generator.generate
185 sys.perfs["code generator"].add clock.lapse
186
187 if opt_verbose.value > 1 then
188 print "# Performance Analysis:"
189 print sys.perfs
190 end