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