1 /*
2 * Copyright 2019 by its authors. See AUTHORS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <iostream>
18 #include <fstream>
19
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <getopt.h>
23 #include <libgen.h>
24
25 #include <string>
26 #include <map>
27 #include <algorithm>
28 #include <stdexcept>
29 #include <iosfwd>
30 #include <type_traits>
31 #include <cassert>
32
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
36 #include <Eolian.h>
37
38 #include <Eina.hh>
39 #include <Eolian_Cxx.hh>
40
41 #include <eolian/mono/logging.hh>
42 #include <eolian/mono/name_helpers.hh>
43 #include <eolian/mono/klass.hh>
44 #include <eolian/mono/enum_definition.hh>
45 #include <eolian/mono/struct_definition.hh>
46 #include <eolian/mono/type_impl.hh>
47 #include <eolian/mono/marshall_type_impl.hh>
48 #include <eolian/mono/marshall_annotation.hh>
49 #include <eolian/mono/function_pointer.hh>
50 #include <eolian/mono/alias_definition.hh>
51 #include <eolian/mono/variable_definition.hh>
52
53 namespace eolian_mono {
54
55 /// Program options.
56 struct options_type
57 {
58 std::vector<std::string> include_dirs;
59 std::string in_file;
60 std::string out_file;
61 std::string examples_dir;
62 std::string dllimport;
63 mutable Eolian_State* state;
64 mutable Eolian_Unit const* unit;
65 int v_major;
66 int v_minor;
67 bool want_beta;
68 bool want_partial;
69 std::map<const std::string, std::string> references_map;
70 };
71
72 // Parses a CSV file in the format 'filename,library' (without trimming spaces around ',')
73 static std::vector<std::pair<std::string, std::string> >
parse_reference(std::string filename)74 parse_reference(std::string filename)
75 {
76 std::vector<std::pair<std::string, std::string> > ret;
77 std::string delimiter = ",";
78 std::ifstream infile(filename);
79 std::string line;
80
81 while (std::getline(infile, line))
82 {
83 size_t pos = line.find(delimiter);
84
85 if (pos == std::string::npos)
86 throw std::invalid_argument("Malformed mapping. Must be 'filename,library'");
87
88 std::string eo_filename = line.substr(0, pos);
89 std::string library = line.substr(pos + 1);
90 library[0] = std::toupper(library[0]);
91 ret.push_back(std::pair<std::string, std::string>(eo_filename, library));
92 }
93 return ret;
94 }
95
96 static bool
opts_check(eolian_mono::options_type const & opts)97 opts_check(eolian_mono::options_type const& opts)
98 {
99 if (opts.in_file.empty())
100 {
101 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
102 << "Nothing to generate?" << std::endl;
103 }
104 else if (opts.out_file.empty())
105 {
106 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
107 << "Nowhere to generate?" << std::endl;
108 }
109 else
110 return true; // valid.
111 return false;
112 }
113
114 static void
run(options_type const & opts)115 run(options_type const& opts)
116 {
117 const Eolian_Class *klass = NULL;
118 Eina_Iterator *aliases = NULL;
119 const Eolian_Typedecl *tp = NULL;
120 char* dup = strdup(opts.in_file.c_str());
121 std::string basename_input = basename(dup);
122 klass = ::eolian_state_class_by_file_get(opts.state, basename_input.c_str());
123 aliases= ::eolian_state_aliases_by_file_get(opts.state, basename_input.c_str());
124 free(dup);
125
126 std::string class_file_name = opts.out_file;
127
128 std::ofstream output_file;
129 std::ostream_iterator<char> iterator
130 {[&]
131 {
132 if(opts.out_file == "-")
133 return std::ostream_iterator<char>(std::cout);
134 else
135 {
136 output_file.open(class_file_name);
137 if (!output_file.good())
138 {
139 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
140 << "Can't open output file: " << class_file_name << std::endl;
141 throw std::runtime_error("");
142 }
143 return std::ostream_iterator<char>(output_file);
144 }
145 }()};
146
147 if (!as_generator(
148 "/*\n"
149 " * Copyright 2019 by its authors. See AUTHORS.\n"
150 " *\n"
151 " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
152 " * you may not use this file except in compliance with the License.\n"
153 " * You may obtain a copy of the License at\n"
154 " *\n"
155 " * http://www.apache.org/licenses/LICENSE-2.0\n"
156 " *\n"
157 " * Unless required by applicable law or agreed to in writing, software\n"
158 " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
159 " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
160 " * See the License for the specific language governing permissions and\n"
161 " * limitations under the License.\n"
162 " */\n\n"
163 ).generate(iterator, attributes::unused, efl::eolian::grammar::context_null()))
164 throw std::runtime_error("Failed to generate license notice");
165
166 if (!as_generator("#pragma warning disable CS1591\n").generate(iterator, efl::eolian::grammar::attributes::unused, efl::eolian::grammar::context_null()))
167 throw std::runtime_error("Failed to generate pragma to disable missing docs");
168
169 if (!as_generator("using System;\n"
170 "using System.Runtime.InteropServices;\n"
171 "using System.Collections.Generic;\n"
172 "using System.Linq;\n"
173 "using System.Threading;\n"
174 "using System.ComponentModel;\n"
175 "using System.Diagnostics.CodeAnalysis;\n"
176 "using System.Diagnostics.Contracts;\n")
177 .generate(iterator, efl::eolian::grammar::attributes::unused, efl::eolian::grammar::context_null()))
178 {
179 throw std::runtime_error("Failed to generate file preamble");
180 }
181
182 using efl::eolian::grammar::context_add_tag;
183
184 auto context = context_add_tag(eolian_mono::indentation_context{0},
185 context_add_tag(eolian_mono::eolian_state_context{opts.state},
186 context_add_tag(eolian_mono::options_context{opts.want_beta,
187 opts.examples_dir},
188 context_add_tag(eolian_mono::library_context{opts.dllimport,
189 opts.v_major,
190 opts.v_minor,
191 opts.references_map},
192 efl::eolian::grammar::context_null()))));
193
194 EINA_ITERATOR_FOREACH(aliases, tp)
195 {
196 if (eolian_typedecl_type_get(tp) == EOLIAN_TYPEDECL_FUNCTION_POINTER)
197 {
198 const Eolian_Function *fp = eolian_typedecl_function_pointer_get(tp);
199 efl::eolian::grammar::attributes::function_def function_def(fp, EOLIAN_FUNCTION_POINTER, tp, opts.unit);
200 if (!eolian_mono::function_pointer.generate(iterator, function_def, context))
201 throw std::runtime_error("Failed to generate function pointer wrapper");
202 }
203 else // Regular aliases
204 {
205 efl::eolian::grammar::attributes::alias_def alias(tp, opts.unit);
206 auto alias_cxt = context_add_tag(class_context{class_context::alias}, context);
207
208 if (!eolian_mono::alias_definition.generate(iterator, alias, alias_cxt))
209 throw std::runtime_error("Failed to generate alias.");
210 }
211 }
212 ::eina_iterator_free(aliases);
213
214
215 // Constants
216 {
217 auto var_cxt = context_add_tag(class_context{class_context::variables}, context);
218 for (efl::eina::iterator<const Eolian_Constant> var_iterator( ::eolian_state_constants_by_file_get(opts.state, basename_input.c_str()))
219 , var_last; var_iterator != var_last; ++var_iterator)
220 {
221 efl::eolian::grammar::attributes::constant_def var(&*var_iterator, opts.unit);
222 if (!eolian_mono::constant_definition.generate(iterator, var, var_cxt))
223 {
224 throw std::runtime_error("Failed to generate enum");
225 }
226 }
227 }
228
229 if (klass)
230 {
231 efl::eolian::grammar::attributes::klass_def klass_def(klass, opts.unit);
232 std::vector<efl::eolian::grammar::attributes::klass_def> klasses{klass_def};
233
234 auto klass_gen = !opts.want_partial ? eolian_mono::klass
235 : eolian_mono::klass(eolian_mono::class_partial);
236 if (!klass_gen.generate(iterator, klass_def, context))
237 {
238 throw std::runtime_error("Failed to generate class");
239 }
240 }
241
242 // Enums
243 for (efl::eina::iterator<const Eolian_Typedecl> enum_iterator( ::eolian_state_enums_by_file_get(opts.state, basename_input.c_str()))
244 , enum_last; enum_iterator != enum_last; ++enum_iterator)
245 {
246 efl::eolian::grammar::attributes::enum_def enum_(&*enum_iterator, opts.unit);
247 auto enum_cxt = context_add_tag(class_context{class_context::enums}, context);
248 if (!eolian_mono::enum_definition.generate(iterator, enum_, enum_cxt))
249 {
250 throw std::runtime_error("Failed to generate enum");
251 }
252 }
253
254 // Structs
255 for (efl::eina::iterator<const Eolian_Typedecl> struct_iterator( ::eolian_state_structs_by_file_get(opts.state, basename_input.c_str()))
256 , struct_last; struct_iterator != struct_last; ++struct_iterator)
257 {
258 efl::eolian::grammar::attributes::struct_def struct_(&*struct_iterator, opts.unit);
259 auto structs_cxt = context_add_tag(class_context{class_context::structs}, context);
260 if (!eolian_mono::struct_entities.generate(iterator, struct_, structs_cxt))
261 {
262 throw std::runtime_error("Failed to generate struct");
263 }
264 }
265 }
266
267 static void
database_load(options_type const & opts)268 database_load(options_type const& opts)
269 {
270 for (auto src : opts.include_dirs)
271 {
272 if (!::eolian_state_directory_add(opts.state, src.c_str()))
273 {
274 EINA_CXX_DOM_LOG_WARN(eolian_mono::domain)
275 << "Couldn't load eolian from '" << src << "'.";
276 }
277 }
278 if (!::eolian_state_all_eot_files_parse(opts.state))
279 {
280 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
281 << "Eolian failed parsing eot files";
282 assert(false && "Error parsing eot files");
283 }
284 if (opts.in_file.empty())
285 {
286 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
287 << "No input file.";
288 assert(false && "Error parsing input file");
289 }
290 if (!::eolian_state_file_path_parse(opts.state, opts.in_file.c_str()))
291 {
292 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain)
293 << "Failed parsing: " << opts.in_file << ".";
294 assert(false && "Error parsing input file");
295 }
296 }
297
298 } // namespace eolian_mono {
299
300 static void
_print_version()301 _print_version()
302 {
303 std::cerr
304 << "Eolian C++ Binding Generator (EFL "
305 << PACKAGE_VERSION << ")" << std::endl;
306 }
307
308 static void
_usage(const char * progname)309 _usage(const char *progname)
310 {
311 std::cerr
312 << progname
313 << " [options] [file.eo]" << std::endl
314 << " A single input file must be provided (unless -a is specified)." << std::endl
315 << "Options:" << std::endl
316 << " -a, --all Generate bindings for all Eo classes." << std::endl
317 << " -c, --class <name> The Eo class name to generate code for." << std::endl
318 << " -D, --out-dir <dir> Output directory where generated code will be written." << std::endl
319 << " -I, --in <file/dir> The source containing the .eo descriptions." << std::endl
320 << " -o, --out-file <file> The output file name. [default: <classname>.eo.cs]" << std::endl
321 << " -n, --namespace <ns> Wrap generated code in a namespace. [Eg: Efl.Ui.Widget]" << std::endl
322 << " -r, --recurse Recurse input directories loading .eo files." << std::endl
323 << " -v, --version Print the version." << std::endl
324 << " -b, --beta Enable @beta methods." << std::endl
325 << " -e, --example-dir <dir> Folder to search for example files." << std::endl
326 << " -p, --partial Create class as a partial class" << std::endl
327 << " -h, --help Print this help." << std::endl;
328 exit(EXIT_FAILURE);
329 }
330
331 static void
_assert_not_dup(std::string option,std::string value)332 _assert_not_dup(std::string option, std::string value)
333 {
334 if (value != "")
335 {
336 EINA_CXX_DOM_LOG_ERR(eolian_mono::domain) <<
337 "Option -" + option + " already set (" + value + ")";
338 }
339 }
340
341 static eolian_mono::options_type
opts_get(int argc,char ** argv)342 opts_get(int argc, char **argv)
343 {
344 eolian_mono::options_type opts{};
345
346 const struct option long_options[] =
347 {
348 { "in", required_argument, 0, 'I' },
349 { "out-file", required_argument, 0, 'o' },
350 { "version", no_argument, 0, 'v' },
351 { "help", no_argument, 0, 'h' },
352 { "dllimport", required_argument, 0, 'l' },
353 { "vmaj", required_argument, 0, 'M' },
354 { "vmin", required_argument, 0, 'm' },
355 { "references", required_argument, 0, 'r'},
356 { "beta", no_argument, 0, 'b'},
357 { "example-dir", required_argument, 0, 'e' },
358 { "partial", no_argument, 0, 'p' },
359 { 0, 0, 0, 0 }
360 };
361 const char* options = "I:D:o:c:M:m:ar:vhbpe:";
362
363 int c, idx;
364 while ( (c = getopt_long(argc, argv, options, long_options, &idx)) != -1)
365 {
366 if (c == 'I')
367 {
368 opts.include_dirs.push_back(optarg);
369 }
370 else if (c == 'o')
371 {
372 _assert_not_dup("o", opts.out_file);
373 opts.out_file = optarg;
374 }
375 else if (c == 'h')
376 {
377 _usage(argv[0]);
378 }
379 else if (c == 'l')
380 {
381 opts.dllimport = optarg;
382 }
383 else if (c == 'M')
384 {
385 opts.v_major = std::stoi(optarg);
386 }
387 else if (c == 'm')
388 {
389 opts.v_minor = std::stoi(optarg);
390 }
391 else if (c == 'r')
392 {
393 try
394 {
395 std::vector<std::pair<std::string, std::string> > names = eolian_mono::parse_reference(optarg);
396 for (auto&& p : names)
397 {
398 opts.references_map[p.first] = p.second;
399 }
400 }
401 catch (const std::invalid_argument &e)
402 {
403 std::cerr << "Invalid argument processing argument " << optarg << std::endl;
404 _usage(argv[0]);
405 assert(false && e.what());
406 }
407 }
408 else if (c == 'v')
409 {
410 _print_version();
411 if (argc == 2) exit(EXIT_SUCCESS);
412 }
413 else if (c == 'b')
414 {
415 opts.want_beta = true;
416 }
417 else if (c == 'e')
418 {
419 opts.examples_dir = optarg;
420 if (!opts.examples_dir.empty() && opts.examples_dir.back() != '/') opts.examples_dir += "/";
421 }
422 else if (c == 'p')
423 {
424 opts.want_partial = true;
425 }
426 }
427 if (optind == argc-1)
428 {
429 opts.in_file = argv[optind];
430 }
431
432 if (!eolian_mono::opts_check(opts))
433 {
434 _usage(argv[0]);
435 assert(false && "Wrong options passed in command-line");
436 }
437
438 return opts;
439 }
440
main(int argc,char ** argv)441 int main(int argc, char **argv)
442 {
443 try
444 {
445 efl::eina::eina_init eina_init;
446 efl::eolian::eolian_init eolian_init;
447 efl::eolian::eolian_state eolian_state;
448 eolian_mono::options_type opts = opts_get(argc, argv);
449 opts.state = eolian_state.value;
450 opts.unit = eolian_state.as_unit();
451 eolian_mono::database_load(opts);
452 eolian_mono::run(opts);
453 }
454 catch(std::exception const& e)
455 {
456 std::cerr << "EOLCXX: Eolian C++ failed generation for the following reason: " << e.what() << std::endl;
457 std::cout << "EOLCXX: Eolian C++ failed generation for the following reason: " << e.what() << std::endl;
458 return -1;
459 }
460 return 0;
461 }
462
463
464