forked from enlightenment/efl
547 lines
18 KiB
C++
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;
|
|
}
|