/* * 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. */ #ifndef EOLIAN_MONO_EVENTS_HH #define EOLIAN_MONO_EVENTS_HH #include #include "grammar/generator.hpp" #include "grammar/klass_def.hpp" #include "type_impl.hh" // For call_match #include "name_helpers.hh" #include "using_decl.hh" namespace eolian_mono { template struct unpack_event_args_visitor { mutable OutputIterator sink; Context const* context; attributes::type_def const& type; typedef unpack_event_args_visitor visitor_type; typedef bool result_type; bool operator()(grammar::attributes::regular_type_def const& regular) const { std::string const& arg = "evt.Info"; std::string arg_type = name_helpers::type_full_managed_name(regular); if (regular.is_struct()) { // Structs are usually passed by pointer to events, like having a ptr<> modifier // Uses implicit conversion from IntPtr return as_generator( " evt.Info" ).generate(sink, attributes::unused, *context); } else if (type.is_ptr) { return as_generator("(" + arg_type + ")Marshal.PtrToStructure(" + arg + ", typeof(" + arg_type + "))") .generate(sink, attributes::unused, *context); } using attributes::regular_type_def; struct match { eina::optional name; std::function function; } /// Sizes taken from https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sizeof const match_table [] = { {"bool", [&arg] { return "Marshal.ReadByte(" + arg + ") != 0"; }} , {"ubyte", [&arg] { return "Marshal.ReadByte(" + arg + ")"; }} , {"byte", [&arg] { return "(sbyte) Marshal.ReadByte(" + arg + ")"; }} , {"char", [&arg] { return "(char) Marshal.ReadByte(" + arg + ")"; }} , {"short", [&arg] { return "Marshal.ReadInt16(" + arg + ")"; }} , {"ushort", [&arg] { return "(ushort) Marshal.ReadInt16(" + arg + ")"; }} , {"int", [&arg] { return "Marshal.ReadInt32(" + arg + ")"; }} , {"uint", [&arg] { return "(uint) Marshal.ReadInt32(" + arg + ")"; }} , {"long", [&arg] { return "Marshal.ReadInt64(" + arg + ")"; }} , {"ulong", [&arg] { return "(ulong) Marshal.ReadInt64(" + arg + ")"; }} , {"llong", [&arg] { return "(long) Marshal.ReadInt64(" + arg + ")"; }} , {"ullong", [&arg] { return "(ulong) Marshal.ReadInt64(" + arg + ")"; }} , {"int8", [&arg] { return "(sbyte)Marshal.ReadByte(" + arg + ")"; }} , {"uint8", [&arg] { return "Marshal.ReadByte(" + arg + ")"; }} , {"int16", [&arg] { return "Marshal.ReadInt16(" + arg + ")"; }} , {"uint16", [&arg] { return "(ushort)Marshal.ReadInt16(" + arg + ")"; }} , {"int32", [&arg] { return "Marshal.ReadInt32(" + arg + ")"; }} , {"uint32", [&arg] { return "(uint) Marshal.ReadInt32(" + arg + ")"; }} // We don't support int128 as csharp has no similar datatype. , {"int64", [&arg] { return "Marshal.ReadInt64(" + arg + ")"; }} , {"uint64", [&arg] { return "(ulong) Marshal.ReadInt64(" + arg + ")"; }} , {"float", [&arg] { return "Eina.PrimitiveConversion.PointerToManaged(" + arg + ")"; }} , {"double", [&arg] { return "Eina.PrimitiveConversion.PointerToManaged(" + arg + ")"; }} , {"string", [&arg] { return "Eina.StringConversion.NativeUtf8ToManagedString(" + arg + ")"; }} , {"stringshare", [&arg] { return "Eina.StringConversion.NativeUtf8ToManagedString(" + arg + ")"; }} , {"Eina.Error", [&arg] { return "(Eina.Error)Marshal.PtrToStructure(" + arg + ", typeof(Eina.Error))"; }} }; std::string full_type_name = name_helpers::type_full_eolian_name(regular); auto filter_func = [®ular, &full_type_name] (match const& m) { return (!m.name || *m.name == regular.base_type || *m.name == full_type_name); }; auto accept_func = [&](std::string const& conversion) { return as_generator(conversion).generate(sink, attributes::unused, *context); }; if (eina::optional b = call_match(match_table, filter_func, accept_func)) return *b; else { // Type defined in Eo is passed here. (e.g. enum type defined in Eo) // Uses conversion from IntPtr with type casting to the given type. return as_generator( " (" << arg_type << ")evt.Info" ).generate(sink, attributes::unused, *context); } } bool operator()(grammar::attributes::klass_name const& cls) const { return as_generator("(Efl.Eo.Globals.CreateWrapperFor(evt.Info) as " + name_helpers::klass_full_concrete_name(cls) + ")").generate(sink, attributes::unused, *context); } bool operator()(attributes::complex_type_def const&) const { return as_generator("new " << eolian_mono::type << "(evt.Info, false, false)").generate(sink, type, *context); } }; template struct pack_event_info_and_call_visitor { mutable OutputIterator sink; Context const* context; attributes::type_def const& type; static auto constexpr native_call = "Efl.Eo.Globals.CallEventCallback(this.NativeHandle, desc, info);\n"; typedef pack_event_info_and_call_visitor visitor_type; typedef bool result_type; bool operator()(grammar::attributes::regular_type_def const& regular) const { std::string arg_type = name_helpers::type_full_managed_name(regular); auto const& indent = current_indentation(*context); if (regular.is_struct()) { return as_generator( indent << "IntPtr info = Marshal.AllocHGlobal(Marshal.SizeOf(e.arg));\n" << indent << "try\n" << indent << "{\n" << indent << scope_tab << "Marshal.StructureToPtr(e.arg, info, false);\n" << indent << scope_tab << this->native_call << indent << "}\n" << indent << "finally\n" << indent << "{\n" << indent << scope_tab << "Marshal.FreeHGlobal(info);\n" << indent << "}\n" ).generate(sink, attributes::unused, *context); } using attributes::regular_type_def; struct match { eina::optional name; std::function function; }; std::string full_type_name = name_helpers::type_full_eolian_name(regular); auto filter_func = [®ular, &full_type_name] (match const& m) { return (!m.name || *m.name == regular.base_type || *m.name == full_type_name); }; match const str_table[] = { {"string", [] { return "e.arg"; }} , {"stringshare", [] { return "e.arg"; }} }; auto str_accept_func = [&](std::string const& conversion) { return as_generator( indent << "IntPtr info = Eina.StringConversion.ManagedStringToNativeUtf8Alloc(" << conversion << ");\n" << indent << "try\n" << indent << "{\n" << indent << scope_tab << this->native_call << indent << "}\n" << indent << "finally\n" << indent << "{\n" << indent << scope_tab << "Eina.MemoryNative.Free(info);\n" << indent << "}\n").generate(sink, attributes::unused, *context); }; if (eina::optional b = call_match(str_table, filter_func, str_accept_func)) return *b; match const value_table [] = { {"bool", [] { return "e.arg ? (byte) 1 : (byte) 0"; }} , {"Eina.Error", [] { return "(int)e.arg"; }} , {nullptr, [] { return "e.arg"; }} }; auto value_accept_func = [&](std::string const& conversion) { return as_generator( indent << "IntPtr info = Eina.PrimitiveConversion.ManagedToPointerAlloc(" << conversion << ");\n" << indent << "try\n" << indent << "{\n" << indent << scope_tab << this->native_call << indent << "}\n" << indent << "finally\n" << indent << "{\n" << indent << scope_tab << "Marshal.FreeHGlobal(info);\n" << indent << "}\n").generate(sink, attributes::unused, *context); }; if (eina::optional b = call_match(value_table, filter_func, value_accept_func)) return *b; return value_accept_func("e.args"); } bool operator()(grammar::attributes::klass_name const&) const { auto const& indent = current_indentation(*context); return as_generator(indent << "IntPtr info = e.arg.NativeHandle;\n" << indent << this->native_call).generate(sink, attributes::unused, *context); } bool operator()(attributes::complex_type_def const&) const { auto const& indent = current_indentation(*context); return as_generator(indent << "IntPtr info = e.arg.Handle;\n" << indent << this->native_call).generate(sink, attributes::unused, *context); } }; /* * Generates a struct wrapping the argument of a given event. */ struct event_argument_wrapper_generator { template bool generate(OutputIterator sink, attributes::event_def const& evt, Context const& context) const { efl::eina::optional etype = evt.type; if (!etype.is_engaged()) return true; if (blacklist::is_event_blacklisted(evt, context)) return true; std::string evt_name = name_helpers::managed_event_name(evt.name); if (!as_generator("/// Event argument wrapper for event .\n" ).generate(sink, nullptr, context)) return false; std::string since; if (!evt.is_beta) { since = evt.documentation.since; if (since.empty()) { auto unit = (const Eolian_Unit*) context_find_tag(context).state; attributes::klass_def klass(get_klass(evt.klass, unit), unit); since = klass.documentation.since; } if (!since.empty()) { if (!as_generator( lit("/// Since EFL ") << evt.documentation.since << ".\n" ).generate(sink, nullptr, context)) return false; } else { EINA_CXX_DOM_LOG_ERR(eolian_mono::domain) << "Event " << evt.name << " of class " << evt.klass.eolian_name << " is stable but has no 'Since' information."; // We do not bail out here because there are some cases of this happening upstream. } } if (!as_generator(lit("/// \n") << "[Efl.Eo.BindingEntity]\n" << "public class " << name_helpers::managed_event_args_short_name(evt) << " : EventArgs {\n" << scope_tab << "/// Actual event payload.\n" ).generate(sink, nullptr, context)) return false; if (since != "") { if (!as_generator(scope_tab << "/// Since EFL " << since << ".\n").generate(sink, nullptr, context)) return false; } if (!as_generator(scope_tab << "/// \n" << scope_tab << "/// " << documentation_string << "\n" << scope_tab << "public " << type << " arg { get; set; }\n" << "}\n\n" ).generate(sink, std::make_tuple(evt.documentation.summary, *etype), context)) return false; return true; } } const event_argument_wrapper {}; /* * Generates an event declaration as a C# Interface member. * In regular/abstract classes they are declared directly in their * implementation in event_definition_generator. */ struct event_declaration_generator { template bool generate(OutputIterator sink, attributes::event_def const& evt, Context const& context) const { std::string wrapper_args_type; std::string evt_name = name_helpers::managed_event_name(evt.name); if (blacklist::is_event_blacklisted(evt, context)) return true; if (evt.type.is_engaged()) wrapper_args_type = "<" + name_helpers::managed_event_args_name(evt) + ">"; if (!as_generator(documentation(1)) .generate(sink, evt, context)) return false; if (evt.type.is_engaged()) if (!as_generator( scope_tab << "/// \n" ).generate(sink, evt, context)) return false; if (!as_generator( scope_tab << "event EventHandler" << wrapper_args_type << " " << evt_name << ";\n" ).generate(sink, evt, context)) return false; return true; } } const event_declaration {}; struct event_definition_generator { attributes::klass_def const& klass; attributes::klass_def const& leaf_klass; bool is_inherited_event; template bool generate(OutputIterator sink, attributes::event_def const& evt, Context const& context) const { if (blacklist::is_event_blacklisted(evt, context)) return true; std::string managed_evt_name = name_helpers::managed_event_name(evt.name); auto const& indent = current_indentation(context); bool is_unique = helpers::is_unique_event(evt, leaf_klass); bool use_explicit_impl = is_inherited_event && !is_unique; // The name of the public event that goes in the public API. std::string wrapper_evt_name; if (use_explicit_impl) wrapper_evt_name = name_helpers::translate_inherited_event_name(evt, klass); else wrapper_evt_name = managed_evt_name; std::string klass_name; if (is_inherited_event) klass_name = name_helpers::klass_full_interface_name(klass); else klass_name = name_helpers::klass_concrete_name(klass); std::string wrapper_args_type = "EventArgs"; std::string wrapper_args_template = ""; std::string event_args = "EventArgs args = EventArgs.Empty;\n"; std::string event_native_call; efl::eina::optional etype = evt.type; if (!etype.is_engaged()) { auto event_call_site_sink = std::back_inserter(event_native_call); if (!as_generator(indent.inc().inc() << "Efl.Eo.Globals.CallEventCallback(this.NativeHandle, desc, IntPtr.Zero);\n") .generate(event_call_site_sink, attributes::unused, context)) return false; } else { wrapper_args_type = name_helpers::managed_event_args_name(evt); wrapper_args_template = "<" + wrapper_args_type + ">"; std::string arg_initializer; auto arg_initializer_sink = std::back_inserter(arg_initializer); auto event_call_site_sink = std::back_inserter(event_native_call); auto sub_context = change_indentation(indent.inc().inc(), context); if (!as_generator(wrapper_args_type << " args = new " << wrapper_args_type << "();\n" << scope_tab(6) << "args.arg = ").generate(arg_initializer_sink, attributes::unused, context)) return false; if (!(*etype).original_type.visit(unpack_event_args_visitor{arg_initializer_sink, &sub_context, *etype})) return false; if (!(*etype).original_type.visit(pack_event_info_and_call_visitor{event_call_site_sink, &sub_context, *etype})) return false; arg_initializer += ";\n"; event_args = arg_initializer; } if(!as_generator(documentation(1)).generate(sink, evt, context)) return false; if (etype.is_engaged()) if (!as_generator( scope_tab << "/// \n" ).generate(sink, evt, context)) return false; // Visible event declaration. Either a regular class member or an explicit interface implementation. if (klass.type == attributes::class_type::interface_ || klass.type == attributes::class_type::mixin) { // Public event implementation. if (!as_generator( scope_tab << (!use_explicit_impl ? "public " : " ") << "event EventHandler" << wrapper_args_template << " " << (use_explicit_impl ? (klass_name + ".") : "") << managed_evt_name << "\n" ).generate(sink, attributes::unused, context)) return false; } else // For inheritable classes event handling. { // We may have inherited an event with the same name as this concrete event, thus // the concrete event would "hide" the interface one, requiring the new keyword. std::string visibility = "public "; if (!is_unique) visibility += "new "; if (!as_generator( scope_tab << visibility << "event EventHandler" << wrapper_args_template << " " << wrapper_evt_name << "\n" ).generate(sink, attributes::unused, context)) return false; } if (!generate_event_add_remove(sink, evt, event_args, context)) return false; if (!generate_event_trigger(sink, evt, wrapper_evt_name, wrapper_args_type, event_native_call, context)) return false; return true; } template bool generate_event_trigger(OutputIterator sink , attributes::event_def const &evt , std::string const& event_name , std::string const& event_args_type , std::string const& event_native_call , Context const& context) const { auto library_name = context_find_tag(context).actual_library_name(klass.filename); std::string upper_c_name = utils::to_uppercase(evt.c_name); if (!as_generator( scope_tab << "/// Method to raise event "<< event_name << ".\n" ).generate(sink, nullptr, context)) return false; if (!evt.is_beta) { std::string since = evt.documentation.since; if (since.empty()) { auto unit = (const Eolian_Unit*) context_find_tag(context).state; attributes::klass_def klass(get_klass(evt.klass, unit), unit); since = klass.documentation.since; } if (!since.empty()) { if (!as_generator( scope_tab << "/// Since EFL " << evt.documentation.since << ".\n" ).generate(sink, nullptr, context)) return false; } else { EINA_CXX_DOM_LOG_ERR(eolian_mono::domain) << "Event " << evt.name << " of class " << evt.klass.eolian_name << " is stable but has no 'Since' information."; // We do not bail out here because there are some cases of this happening upstream. } } if (!as_generator( scope_tab << "/// \n" << scope_tab << "/// Event to raise.\n" << scope_tab << "public void On" << event_name << "(" << event_args_type << " e)\n" << scope_tab << "{\n" << scope_tab << scope_tab << "var key = \"_" << upper_c_name << "\";\n" << scope_tab << scope_tab << "IntPtr desc = Efl.EventDescription.GetNative(" << library_name << ", key);\n" << scope_tab << scope_tab << "if (desc == IntPtr.Zero)\n" << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << "Eina.Log.Error($\"Failed to get native event {key}\");\n" << scope_tab << scope_tab << scope_tab << "return;\n" << scope_tab << scope_tab << "}\n\n" << event_native_call << scope_tab << "}\n\n" ).generate(sink, nullptr, context)) return false; return true; } template bool generate_event_add_remove(OutputIterator sink , attributes::event_def const &evt , std::string const& event_args , Context const& context) const { std::string upper_c_name = utils::to_uppercase(evt.c_name); auto unit = (const Eolian_Unit*) context_find_tag(context).state; attributes::klass_def klass(get_klass(evt.klass, unit), unit); auto library_name = context_find_tag(context).actual_library_name(klass.filename); return as_generator( scope_tab << "{\n" << scope_tab << scope_tab << "add\n" << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << "Efl.EventCb callerCb = (IntPtr data, ref Efl.Event.NativeStruct evt) =>\n" << scope_tab << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << scope_tab << "var obj = Efl.Eo.Globals.WrapperSupervisorPtrToManaged(data).Target;\n" << scope_tab << scope_tab << scope_tab << scope_tab << "if (obj != null)\n" << scope_tab << scope_tab << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << event_args << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "try\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "value?.Invoke(obj, args);\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "}\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "catch (Exception e)\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "Eina.Log.Error(e.ToString());\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "Eina.Error.Set(Eina.Error.UNHANDLED_EXCEPTION);\n" << scope_tab << scope_tab << scope_tab << scope_tab << scope_tab << "}\n" << scope_tab << scope_tab << scope_tab << scope_tab << "}\n" << scope_tab << scope_tab << scope_tab << "};\n\n" << scope_tab << scope_tab << scope_tab << "string key = \"_" << upper_c_name << "\";\n" << scope_tab << scope_tab << scope_tab << "AddNativeEventHandler(" << library_name << ", key, callerCb, value);\n" << scope_tab << scope_tab << "}\n\n" << scope_tab << scope_tab << "remove\n" << scope_tab << scope_tab << "{\n" << scope_tab << scope_tab << scope_tab << "string key = \"_" << upper_c_name << "\";\n" << scope_tab << scope_tab << scope_tab << "RemoveNativeEventHandler(" << library_name << ", key, value);\n" << scope_tab << scope_tab << "}\n" << scope_tab << "}\n\n" ).generate(sink, attributes::unused, context); } }; struct event_definition_parameterized { event_definition_generator operator()(attributes::klass_def const& klass, attributes::klass_def const& leaf_klass) const { bool is_inherited_event = klass != leaf_klass; return {klass, leaf_klass, is_inherited_event}; } } const event_definition; } namespace efl { namespace eolian { namespace grammar { template <> struct is_eager_generator : std::true_type {}; template <> struct is_generator : std::true_type {}; template <> struct is_eager_generator : std::true_type {}; template <> struct is_generator : std::true_type {}; template <> struct is_eager_generator : std::true_type {}; template <> struct is_generator : std::true_type {}; template <> struct is_generator : std::true_type {}; namespace type_traits { template <> struct attributes_needed : std::integral_constant {}; template <> struct attributes_needed : std::integral_constant {}; template <> struct attributes_needed : std::integral_constant {}; template <> struct attributes_needed : std::integral_constant {}; } } } } #endif