efl/src/bin/eolian_cxx/eolian_cxx.cc

547 lines
18 KiB
C++

/*
* Copyright 2019 by its authors. See AUTHORS.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include <string>
#include <algorithm>
#include <stdexcept>
#include <iosfwd>
#include <type_traits>
#include <cassert>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Eolian.h>
#include <Eina.hh>
#include <Eolian_Cxx.hh>
#include "grammar/klass_def.hpp"
#include "grammar/header.hpp"
#include "grammar/impl_header.hpp"
#include "grammar/types_definition.hpp"
namespace eolian_cxx {
/// Program options.
struct options_type
{
std::vector<std::string> include_dirs;
std::vector<std::string> in_files;
mutable Eolian_State* state;
mutable Eolian_Unit const* unit;
std::string out_file;
bool main_header;
options_type() : main_header(false) {}
};
static efl::eina::log_domain domain("eolian_cxx");
static bool
opts_check(eolian_cxx::options_type const& opts)
{
if (opts.in_files.empty())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Nothing to generate?" << std::endl;
}
else
{
return true; // valid.
}
return false;
}
static bool
generate(const Eolian_Class* klass, eolian_cxx::options_type const& opts,
std::string const& cpp_types_header)
{
std::string header_decl_file_name = opts.out_file.empty()
? (::eolian_object_file_get((const Eolian_Object *)klass) + std::string(".hh")) : opts.out_file;
std::string header_impl_file_name = header_decl_file_name;
std::size_t dot_pos = header_impl_file_name.rfind(".hh");
if (dot_pos != std::string::npos)
header_impl_file_name.insert(dot_pos, ".impl");
else
header_impl_file_name.insert(header_impl_file_name.size(), ".impl");
efl::eolian::grammar::attributes::klass_def klass_def(klass, opts.unit);
std::vector<efl::eolian::grammar::attributes::klass_def> klasses{klass_def};
std::set<efl::eolian::grammar::attributes::klass_def> forward_klasses{};
std::set<std::string> c_headers;
std::set<std::string> cpp_headers;
c_headers.insert(eolian_object_file_get((const Eolian_Object *)klass) + std::string(".h"));
std::function<void(efl::eolian::grammar::attributes::type_def const&)>
variant_function;
auto klass_name_function
= [&] (efl::eolian::grammar::attributes::klass_name const& name)
{
Eolian_Class const* klass2 = get_klass(name, opts.unit);
assert(klass2);
c_headers.insert(eolian_object_file_get((const Eolian_Object *)klass2) + std::string(".h"));
cpp_headers.insert(eolian_object_file_get((const Eolian_Object *)klass2) + std::string(".hh"));
efl::eolian::grammar::attributes::klass_def cls{klass2, opts.unit};
forward_klasses.insert(cls);
};
auto complex_function
= [&] (efl::eolian::grammar::attributes::complex_type_def const& complex)
{
for(auto&& t : complex.subtypes)
{
variant_function(t);
}
};
variant_function = [&] (efl::eolian::grammar::attributes::type_def const& type)
{
if(efl::eolian::grammar::attributes::klass_name const*
name = efl::eina::get<efl::eolian::grammar::attributes::klass_name>
(&type.original_type))
klass_name_function(*name);
else if(efl::eolian::grammar::attributes::complex_type_def const*
complex = efl::eina::get<efl::eolian::grammar::attributes::complex_type_def>
(&type.original_type))
complex_function(*complex);
};
std::function<void(Eolian_Class const*)> klass_function
= [&] (Eolian_Class const* klass2)
{
if(::eolian_class_parent_get(klass2))
{
Eolian_Class const* inherit = ::eolian_class_parent_get(klass2);
c_headers.insert(eolian_object_file_get((const Eolian_Object *)inherit) + std::string(".h"));
cpp_headers.insert(eolian_object_file_get((const Eolian_Object *)inherit) + std::string(".hh"));
efl::eolian::grammar::attributes::klass_def klass3{inherit, opts.unit};
forward_klasses.insert(klass3);
klass_function(inherit);
}
for(efl::eina::iterator<Eolian_Class const> inherit_iterator ( ::eolian_class_extensions_get(klass2))
, inherit_last; inherit_iterator != inherit_last; ++inherit_iterator)
{
Eolian_Class const* inherit = &*inherit_iterator;
c_headers.insert(eolian_object_file_get((const Eolian_Object *)inherit) + std::string(".h"));
cpp_headers.insert(eolian_object_file_get((const Eolian_Object *)inherit) + std::string(".hh"));
efl::eolian::grammar::attributes::klass_def klass3{inherit, opts.unit};
forward_klasses.insert(klass3);
klass_function(inherit);
}
efl::eolian::grammar::attributes::klass_def klass2_def(klass2, opts.unit);
for(auto&& f : klass2_def.functions)
{
variant_function(f.return_type);
for(auto&& p : f.parameters)
{
variant_function(p.type);
}
}
for(auto&& e : klass2_def.events)
{
if(e.type)
variant_function(*e.type);
}
};
klass_function(klass);
for(efl::eina::iterator<Eolian_Part const> parts_itr ( ::eolian_class_parts_get(klass))
, parts_last; parts_itr != parts_last; ++parts_itr)
{
Eolian_Class const* eolian_part_klass = ::eolian_part_class_get(&*parts_itr);
efl::eolian::grammar::attributes::klass_def part_klass(eolian_part_klass, opts.unit);
forward_klasses.insert(part_klass);
}
cpp_headers.erase(eolian_object_file_get((const Eolian_Object *)klass) + std::string(".hh"));
std::string guard_name;
as_generator(*(efl::eolian::grammar::string << "_") << efl::eolian::grammar::string << "_EO_HH")
.generate(std::back_insert_iterator<std::string>(guard_name)
, std::make_tuple(klass_def.namespaces, klass_def.eolian_name)
, efl::eolian::grammar::context_null{});
std::tuple<std::string, std::set<std::string>&, std::set<std::string>&
, std::vector<efl::eolian::grammar::attributes::klass_def>&
, std::set<efl::eolian::grammar::attributes::klass_def> const&
, std::string const&
, std::vector<efl::eolian::grammar::attributes::klass_def>&
, std::vector<efl::eolian::grammar::attributes::klass_def>&
> attributes
{guard_name, c_headers, cpp_headers, klasses, forward_klasses, cpp_types_header, klasses, klasses};
if(opts.out_file == "-")
{
std::ostream_iterator<char> iterator(std::cout);
efl::eolian::grammar::class_header.generate(iterator, attributes, efl::eolian::grammar::context_null());
std::endl(std::cout);
efl::eolian::grammar::impl_header.generate(iterator, klasses, efl::eolian::grammar::context_null());
std::endl(std::cout);
std::flush(std::cout);
std::flush(std::cerr);
}
else
{
std::ofstream header_decl;
header_decl.open(header_decl_file_name);
if (!header_decl.good())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Can't open output file: " << header_decl_file_name << std::endl;
return false;
}
std::ofstream header_impl;
header_impl.open(header_impl_file_name);
if (!header_impl.good())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Can't open output file: " << header_impl_file_name << std::endl;
return false;
}
efl::eolian::grammar::class_header.generate
(std::ostream_iterator<char>(header_decl), attributes, efl::eolian::grammar::context_null());
efl::eolian::grammar::impl_header.generate
(std::ostream_iterator<char>(header_impl), klasses, efl::eolian::grammar::context_null());
header_impl.close();
header_decl.close();
}
return true;
}
static bool
types_generate(std::string const& fname, options_type const& opts,
std::string& cpp_types_header)
{
using namespace efl::eolian::grammar::attributes;
std::vector<function_def> functions;
Eina_Iterator *itr = eolian_state_objects_by_file_get(opts.state, fname.c_str());
/* const */ Eolian_Object *decl;
// Build list of functions with their parameters
while(::eina_iterator_next(itr, reinterpret_cast<void**>(&decl)))
{
Eolian_Object_Type dt = eolian_object_type_get(decl);
if (dt != EOLIAN_OBJECT_TYPEDECL)
continue;
const Eolian_Typedecl *tp = (const Eolian_Typedecl *)decl;
if (eolian_typedecl_is_extern(tp))
continue;
if (::eolian_typedecl_type_get(tp) != EOLIAN_TYPEDECL_FUNCTION_POINTER)
continue;
const Eolian_Function *func = eolian_typedecl_function_pointer_get(tp);
if (!func) return false;
function_def def(func, EOLIAN_FUNCTION_POINTER, tp, opts.unit);
def.c_name = eolian_typedecl_name_get(tp);
std::replace(def.c_name.begin(), def.c_name.end(), '.', '_');
functions.push_back(std::move(def));
}
::eina_iterator_free(itr);
if (functions.empty())
return true;
std::stringstream sink;
sink.write("\n", 1);
if (!efl::eolian::grammar::types_definition
.generate(std::ostream_iterator<char>(sink),
functions, efl::eolian::grammar::context_null()))
return false;
cpp_types_header = sink.str();
return true;
}
static void
run(options_type const& opts)
{
if(!opts.main_header)
{
const Eolian_Class *klass = nullptr;
char* dup = strdup(opts.in_files[0].c_str());
std::string base (basename(dup));
std::string cpp_types_header;
opts.unit = (Eolian_Unit*)opts.state;
klass = ::eolian_state_class_by_file_get(opts.state, base.c_str());
free(dup);
if (klass)
{
if (!types_generate(base, opts, cpp_types_header) ||
!generate(klass, opts, cpp_types_header))
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Error generating: " << ::eolian_class_short_name_get(klass)
<< std::endl;
assert(false && "error generating class");
}
}
else
{
if (!types_generate(base, opts, cpp_types_header))
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Error generating: " << ::eolian_class_short_name_get(klass)
<< std::endl;
assert(false && "error generating class");
}
else
{
std::ofstream header_decl;
header_decl.open(opts.out_file);
if (!header_decl.good())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Can't open output file: " << opts.out_file << std::endl;
assert(false && "error opening file");
}
std::copy (cpp_types_header.begin(), cpp_types_header.end()
, std::ostream_iterator<char>(header_decl));
}
}
}
else
{
std::set<std::string> headers;
std::set<std::string> eo_files;
for(auto&& name : opts.in_files)
{
Eolian_Unit const* unit = ::eolian_state_file_path_parse(opts.state, name.c_str());
if(!unit)
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Failed parsing: " << name << ".";
}
else
{
if(!opts.unit)
opts.unit = unit;
}
char* dup = strdup(name.c_str());
std::string base(basename(dup));
Eolian_Class const* klass = ::eolian_state_class_by_file_get(opts.state, base.c_str());
free(dup);
if (klass)
{
std::string filename = eolian_object_file_get((const Eolian_Object *)klass);
headers.insert(filename + std::string(".hh"));
eo_files.insert(filename);
}
else
{
headers.insert (base + std::string(".hh"));
}
}
using efl::eolian::grammar::header_include_directive;
using efl::eolian::grammar::implementation_include_directive;
auto main_header_grammar =
*header_include_directive // sequence<string>
<< *implementation_include_directive // sequence<string>
;
std::tuple<std::set<std::string>&, std::set<std::string>&> attributes{headers, eo_files};
std::ofstream main_header;
main_header.open(opts.out_file);
if (!main_header.good())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Can't open output file: " << opts.out_file << std::endl;
return;
}
main_header_grammar.generate(std::ostream_iterator<char>(main_header)
, attributes, efl::eolian::grammar::context_null());
}
}
static void
database_load(options_type const& opts)
{
for (auto src : opts.include_dirs)
{
if (!::eolian_state_directory_add(opts.state, src.c_str()))
{
EINA_CXX_DOM_LOG_WARN(eolian_cxx::domain)
<< "Couldn't load eolian from '" << src << "'.";
}
}
if (!::eolian_state_all_eot_files_parse(opts.state))
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Eolian failed parsing eot files";
assert(false && "Error parsing eot files");
}
if (opts.in_files.empty())
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "No input file.";
assert(false && "Error parsing input file");
}
if (!opts.main_header && !::eolian_state_file_path_parse(opts.state, opts.in_files[0].c_str()))
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain)
<< "Failed parsing: " << opts.in_files[0] << ".";
assert(false && "Error parsing input file");
}
}
} // namespace eolian_cxx {
static void
_print_version()
{
std::cerr
<< "Eolian C++ Binding Generator (EFL "
<< PACKAGE_VERSION << ")" << std::endl;
}
static void
_usage(const char *progname)
{
std::cerr
<< progname
<< " [options] [file.eo]" << std::endl
<< " A single input file must be provided (unless -a is specified)." << std::endl
<< "Options:" << std::endl
<< " -a, --all Generate bindings for all Eo classes." << std::endl
<< " -c, --class <name> The Eo class name to generate code for." << std::endl
<< " -D, --out-dir <dir> Output directory where generated code will be written." << std::endl
<< " -I, --in <file/dir> The source containing the .eo descriptions." << std::endl
<< " -o, --out-file <file> The output file name. [default: <classname>.eo.hh]" << std::endl
<< " -n, --namespace <ns> Wrap generated code in a namespace. [Eg: efl::ecore::file]" << std::endl
<< " -r, --recurse Recurse input directories loading .eo files." << std::endl
<< " -v, --version Print the version." << std::endl
<< " -h, --help Print this help." << std::endl;
exit(EXIT_FAILURE);
}
static void
_assert_not_dup(std::string option, std::string value)
{
if (value != "")
{
EINA_CXX_DOM_LOG_ERR(eolian_cxx::domain) <<
"Option -" + option + " already set (" + value + ")";
}
}
static eolian_cxx::options_type
opts_get(int argc, char **argv)
{
eolian_cxx::options_type opts;
const struct option long_options[] =
{
{ "in", required_argument, nullptr, 'I' },
{ "out-file", required_argument, nullptr, 'o' },
{ "version", no_argument, nullptr, 'v' },
{ "help", no_argument, nullptr, 'h' },
{ "main-header", no_argument, nullptr, 'm' },
{ nullptr, 0, nullptr, 0 }
};
const char* options = "I:D:o:c::marvh";
int c, idx;
while ( (c = getopt_long(argc, argv, options, long_options, &idx)) != -1)
{
if (c == 'I')
{
opts.include_dirs.push_back(optarg);
}
else if (c == 'o')
{
_assert_not_dup("o", opts.out_file);
opts.out_file = optarg;
}
else if (c == 'h')
{
_usage(argv[0]);
}
else if(c == 'm')
{
opts.main_header = true;
}
else if (c == 'v')
{
_print_version();
if (argc == 2) exit(EXIT_SUCCESS);
}
}
if (optind != argc)
{
for(int i = optind; i != argc; ++i)
opts.in_files.push_back(argv[i]);
}
if (!eolian_cxx::opts_check(opts))
{
_usage(argv[0]);
assert(false && "Wrong options passed in command-line");
}
return opts;
}
int main(int argc, char **argv)
{
try
{
efl::eina::eina_init eina_init;
efl::eolian::eolian_init eolian_init;
efl::eolian::eolian_state eolian_state;
eolian_cxx::options_type opts = opts_get(argc, argv);
opts.state = eolian_state.value;
eolian_cxx::database_load(opts);
eolian_cxx::run(opts);
}
catch(std::exception const& e)
{
std::cerr << "EOLCXX: Eolian C++ failed generation for the following reason: " << e.what() << std::endl;
std::cout << "EOLCXX: Eolian C++ failed generation for the following reason: " << e.what() << std::endl;
return -1;
}
return 0;
}