From c84580cc050a0cb7c1ee99f1348a224f2f9f49ed Mon Sep 17 00:00:00 2001 From: Xavi Artigas Date: Fri, 21 Dec 2018 14:52:11 +0100 Subject: [PATCH] doc: Turn comments from EO to C# XML syntax Summary: This allows them to be nicely rendered by IDEs and automatic documentation generators like DocFX. The conversion includes things like turning $name to name or solving references to objects, which in turn requires converting from EO object names to C# names. It uses the same helper methods used to generate the C# object names, so if these change in the future, the references in the comments will change too. Additionally, this patch fixes some minor bugs, like tags outside tags, misspelled tags or missing documentation for getter methods. Fixes T7453 Reviewers: lauromoura, vitor.sousa Reviewed By: lauromoura Subscribers: cedric, #reviewers, #committers Tags: #efl Maniphest Tasks: T7453 Differential Revision: https://phab.enlightenment.org/D7467 --- src/bin/eolian_mono/eolian/mono/blacklist.hh | 9 +- .../eolian_mono/eolian/mono/documentation.hh | 238 ++++++++++++++---- .../eolian/mono/generation_contexts.hh | 5 + .../eolian_mono/eolian/mono/name_helpers.hh | 24 +- src/bin/eolian_mono/eolian_mono.cc | 8 +- src/lib/eolian_cxx/grammar/klass_def.hpp | 24 +- 6 files changed, 231 insertions(+), 77 deletions(-) diff --git a/src/bin/eolian_mono/eolian/mono/blacklist.hh b/src/bin/eolian_mono/eolian/mono/blacklist.hh index 646452e57c..684b842c24 100644 --- a/src/bin/eolian_mono/eolian/mono/blacklist.hh +++ b/src/bin/eolian_mono/eolian/mono/blacklist.hh @@ -77,14 +77,19 @@ inline bool is_alias_blacklisted(attributes::alias_def const& alias) return name_helpers::alias_full_eolian_name(alias) == "Eina.Error"; } -inline bool is_property_blacklisted(attributes::property_def const& property) +inline bool is_property_blacklisted(std::string const& name) { - auto name = name_helpers::klass_full_concrete_or_interface_name(property.klass) + "." + name_helpers::property_managed_name(property); return name == "Efl.Input.Key.Key" || name == "Efl.Input.Hold.Hold" || name == "Efl.Text.Text"; } +inline bool is_property_blacklisted(attributes::property_def const& property) +{ + auto name = name_helpers::klass_full_concrete_or_interface_name(property.klass) + "." + name_helpers::property_managed_name(property); + return is_property_blacklisted(name); +} + } } diff --git a/src/bin/eolian_mono/eolian/mono/documentation.hh b/src/bin/eolian_mono/eolian/mono/documentation.hh index f78c58a9d4..4e9fc5d4b1 100644 --- a/src/bin/eolian_mono/eolian/mono/documentation.hh +++ b/src/bin/eolian_mono/eolian/mono/documentation.hh @@ -6,6 +6,8 @@ #include "grammar/html_escaped_string.hpp" #include "using_decl.hh" #include "name_helpers.hh" +#include "generation_contexts.hh" +#include "blacklist.hh" #include @@ -19,14 +21,185 @@ struct documentation_generator documentation_generator(int scope_size) : scope_size(scope_size) {} + + // Returns the number of parameters (values + keys) that a property method requires + // Specify if you want the Setter or the Getter method. + static int property_num_parameters(const ::Eolian_Function *function, ::Eolian_Function_Type ftype) + { + Eina_Iterator *itr = ::eolian_property_keys_get(function, ftype); + Eolian_Function_Parameter *pr; + int n = 0; + EINA_ITERATOR_FOREACH(itr, pr) { n++; } + eina_iterator_free(itr); + itr = ::eolian_property_values_get(function, ftype); + EINA_ITERATOR_FOREACH(itr, pr) { n++; } + eina_iterator_free(itr); + return n; + } + + // Turns a function name from EO convention to EFL# convention. + // The name_tail parameter is the last 4 chars of the original string, which + // could be ".set" or ".get" and in this case they are ignored by Eolian. + // We want them to know what the documentation intended to reference. + static std::string function_conversion(const ::Eolian_Object *klass, const ::Eolian_Function *function, std::string name_tail) + { + ::Eolian_Function_Type ftype = ::eolian_function_type_get(function); + const char* eo_name = ::eolian_function_name_get(function); + std::string name = name_helpers::managed_namespace(::eolian_object_name_get(klass)); + switch(ftype) + { + case ::EOLIAN_METHOD: + if (blacklist::is_function_blacklisted( + ::eolian_function_full_c_name_get(function, ftype, EINA_FALSE))) return ""; + name += "."; + name += name_helpers::managed_method_name( + ::eolian_object_short_name_get(klass), eo_name); + break; + case ::EOLIAN_PROP_SET: + name += ".Set"; + name += name_helpers::property_managed_name(eo_name); + break; + case ::EOLIAN_PROP_GET: + name += ".Get"; + name += name_helpers::property_managed_name(eo_name); + break; + case ::EOLIAN_PROPERTY: + { + int getter_params = property_num_parameters(function, ::EOLIAN_PROP_GET); + int setter_params = property_num_parameters(function, ::EOLIAN_PROP_SET); + std::string short_name = name_helpers::property_managed_name(eo_name); + bool blacklisted = blacklist::is_property_blacklisted(name + "." + short_name); + // EO properties with keys, with more than one value, or blacklisted, are not + // converted into C# properties. + // In these cases we refer to the getter method instead of the property. + if ((getter_params > 1) || (setter_params > 1) || (blacklisted)) name += ".Get" + short_name; + else if (name_tail == ".get") name += ".Get" + short_name; + else if (name_tail == ".set") name += ".Set" + short_name; + else name += "." + short_name; + } + break; + default: + break; + } + return name; + } + + // Turns an Eolian reference like @Efl.Input.Pointer.tool into a tag + static std::string ref_conversion(const ::Eolian_Doc_Token *token, const Eolian_State *state, std::string name_tail) + { + const Eolian_Object *data, *data2; + ::Eolian_Object_Type type = + ::eolian_doc_token_ref_resolve(token, state, &data, &data2); + std::string ref; + switch(type) + { + case ::EOLIAN_OBJECT_STRUCT_FIELD: + ref = name_helpers::managed_namespace(::eolian_object_name_get(data)); + ref += "."; + ref += ::eolian_object_name_get(data2); + if (blacklist::is_struct_blacklisted(ref)) return ""; + break; + case ::EOLIAN_OBJECT_EVENT: + ref = name_helpers::managed_namespace(::eolian_object_name_get(data)); + ref += "."; + ref += name_helpers::managed_event_name(::eolian_object_name_get(data2)); + break; + case ::EOLIAN_OBJECT_ENUM_FIELD: + ref = name_helpers::managed_namespace(::eolian_object_name_get(data)); + ref += "."; + ref += name_helpers::enum_field_managed_name(::eolian_object_name_get(data2)); + break; + case ::EOLIAN_OBJECT_FUNCTION: + ref += function_conversion(data, (const ::Eolian_Function *)data2, name_tail); + break; + case ::EOLIAN_OBJECT_UNKNOWN: + // If the reference cannot be resolved, just return an empty string and + // it won't be converted into a tag. + break; + default: + ref = name_helpers::managed_namespace(::eolian_object_name_get(data)); + break; + } + return ref; + } + + // Turns EO documentation syntax into C# triple-slash XML comment syntax + static std::string syntax_conversion(std::string text, const Eolian_State *state) + { + std::string new_text, ref; + ::Eolian_Doc_Token token; + const char *text_ptr = text.c_str(); + ::eolian_doc_token_init(&token); + while ((text_ptr = ::eolian_documentation_tokenize(text_ptr, &token)) != NULL) + { + std::string token_text, name_tail; + char *token_text_cstr = ::eolian_doc_token_text_get(&token); + if (token_text_cstr) + { + token_text = token_text_cstr; + free(token_text_cstr); + if (token_text.length() > 4) + name_tail = token_text.substr(token_text.size() - 4, 4); + } + switch(::eolian_doc_token_type_get(&token)) + { + case ::EOLIAN_DOC_TOKEN_TEXT: + new_text += token_text; + break; + case ::EOLIAN_DOC_TOKEN_REF: + ref = ref_conversion(&token, state, name_tail); + if (ref != "") + new_text += ""; + else + // Unresolved references are passed through. + // They will appear in the docs as plain text, without link, + // but at least they won't be removed by DocFX. + new_text += token_text; + break; + case ::EOLIAN_DOC_TOKEN_MARK_NOTE: + new_text += "NOTE: " + token_text; + break; + case ::EOLIAN_DOC_TOKEN_MARK_WARNING: + new_text += "WARNING: " + token_text; + break; + case ::EOLIAN_DOC_TOKEN_MARK_REMARK: + new_text += "REMARK: " + token_text; + break; + case ::EOLIAN_DOC_TOKEN_MARK_TODO: + new_text += "TODO: " + token_text; + break; + case ::EOLIAN_DOC_TOKEN_MARKUP_MONOSPACE: + new_text += "" + token_text + ""; + break; + default: + break; + } + } + return new_text; + } + /// Tag generator helpers template - bool generate_tag(OutputIterator sink, std::string const& tag, std::string const &text, Context const& context) const + bool generate_tag(OutputIterator sink, std::string const& tag, std::string const &text, Context const& context, std::string tag_params = "") const { - if (text.empty()) - return true; + std::string new_text; + if (!as_generator(html_escaped_string).generate(std::back_inserter(new_text), text, context)) + return false; + new_text = syntax_conversion( new_text, context_find_tag(context).state ); - return as_generator( scope_tab(scope_size) << "///<" << tag << ">" << html_escaped_string << "\n").generate(sink, text, context); + std::string tabs; + as_generator(scope_tab(scope_size) << "/// ").generate (std::back_inserter(tabs), attributes::unused, context); + + std::istringstream ss(new_text); + std::string para; + std::string final_text = "<" + tag + tag_params + ">"; + bool first = true; + while (std::getline(ss, para)) { + if (first) final_text += para; + else final_text += "\n" + tabs + para; + first = false; + } + return as_generator(scope_tab(scope_size) << "/// " << final_text << "\n").generate(sink, attributes::unused, context); } template @@ -35,23 +208,16 @@ struct documentation_generator return generate_tag(sink, "summary", text, context); } - template - bool generate_tag_para(OutputIterator sink, std::string const& text, Context const& context) const - { - return generate_tag(sink, "para", text, context); - } - template bool generate_tag_param(OutputIterator sink, std::string const& name, std::string const& text, Context const& context) const { - return as_generator( scope_tab(scope_size) << "///" - << html_escaped_string << "\n").generate(sink, text, context); + return generate_tag(sink, "param", text, context, " name=\"" + name + "\""); } template bool generate_tag_return(OutputIterator sink, std::string const& text, Context const& context) const { - return generate_tag(sink, "return", text, context); + return generate_tag(sink, "returns", text, context); } // Actual exported generators @@ -91,7 +257,7 @@ struct documentation_generator if (!generate_parameter(sink, param, context)) return false; - if (!generate_tag_return(sink, func.return_documentation.summary, context)) + if (!generate_tag_return(sink, func.return_documentation.full_text, context)) return false; return true; @@ -107,7 +273,7 @@ struct documentation_generator if (!generate_parameter(sink, param, context)) return false; - if (!generate_tag_return(sink, func.return_documentation.summary, context)) + if (!generate_tag_return(sink, func.return_documentation.full_text, context)) return false; return true; @@ -116,51 +282,13 @@ struct documentation_generator template bool generate_parameter(OutputIterator sink, attributes::parameter_def const& param, Context const& context) const { - return generate_tag_param(sink, name_helpers::escape_keyword(param.param_name), param.documentation.summary, context); + return generate_tag_param(sink, name_helpers::escape_keyword(param.param_name), param.documentation.full_text, context); } template bool generate(OutputIterator sink, attributes::documentation_def const& doc, Context const& context) const { - if (!generate_preamble(sink, doc, context)) - return false; - if (!generate_body(sink, doc, context)) - return false; - if (!generate_epilogue(sink, doc, context)) - return false; - - return true; - } - - template - bool generate_preamble(OutputIterator sink, attributes::documentation_def const& doc, Context const context) const - { - return generate_tag_summary(sink, doc.summary, context); - } - - - template - bool generate_body(OutputIterator sink, attributes::documentation_def const& doc, Context const context) const - { - for (auto&& para : doc.desc_paragraphs) - { - if (!generate_tag_para(sink, para, context)) - return false; - } - - return true; - } - - template - bool generate_epilogue(OutputIterator sink, attributes::documentation_def const& doc, Context const context) const - { - if (doc.since.empty()) - return true; - - if (!generate_tag_para(sink, doc.since, context)) - return false; - - return true; + return generate_tag_summary(sink, doc.full_text, context); } }; diff --git a/src/bin/eolian_mono/eolian/mono/generation_contexts.hh b/src/bin/eolian_mono/eolian/mono/generation_contexts.hh index 7f94de7736..ca1ca9e678 100644 --- a/src/bin/eolian_mono/eolian/mono/generation_contexts.hh +++ b/src/bin/eolian_mono/eolian/mono/generation_contexts.hh @@ -11,6 +11,7 @@ struct class_context inherit, inherit_native, structs, + enums, function_ptr, alias, }; @@ -40,6 +41,10 @@ library_context::actual_library_name(const std::string& filename) const return '"' + library_name + '"'; } +struct eolian_state_context { + const Eolian_State *state; +}; + } #endif diff --git a/src/bin/eolian_mono/eolian/mono/name_helpers.hh b/src/bin/eolian_mono/eolian/mono/name_helpers.hh index cfda7229f6..d953a09f9a 100644 --- a/src/bin/eolian_mono/eolian/mono/name_helpers.hh +++ b/src/bin/eolian_mono/eolian/mono/name_helpers.hh @@ -32,10 +32,6 @@ namespace name_helpers { namespace attributes = efl::eolian::grammar::attributes; namespace detail { -inline bool is_iequal(std::string const& lhs, std::string const& rhs) -{ - return strcasecmp(lhs.c_str(), rhs.c_str()) == 0; -} inline bool is_equal(std::string const& lhs, std::string const& rhs) { return lhs == rhs; @@ -176,21 +172,26 @@ inline std::string managed_namespace(std::string const& ns) return escape_keyword(utils::remove_all(ns, '_')); } -inline std::string managed_method_name(attributes::function_def const& f) +inline std::string managed_method_name(std::string const& klass, std::string const& name) { - std::vector names = utils::split(f.name, '_'); + std::vector names = utils::split(name, '_'); name_helpers::reorder_verb(names); std::string candidate = escape_keyword(utils::to_pascal_case(names)); // Some eolian methods have the same name as their parent class - if (candidate == f.klass.eolian_name) + if (candidate == klass) candidate = "Do" + candidate; return candidate; } +inline std::string managed_method_name(attributes::function_def const& f) +{ + return managed_method_name(f.klass.eolian_name, f.name); +} + inline std::string alias_full_eolian_name(attributes::alias_def const& alias) { return join_namespaces(alias.namespaces, '.') + alias.eolian_name; @@ -243,14 +244,19 @@ inline std::string to_field_name(std::string const& in) return utils::capitalize(in); } -inline std::string property_managed_name(attributes::property_def const& property) +inline std::string property_managed_name(const std::string name) { - auto names = utils::split(property.name, '_'); + auto names = utils::split(name, '_'); // No need to escape keyword here as it will be capitalized and already // namespaced inside the owner class. return utils::to_pascal_case(names); } +inline std::string property_managed_name(attributes::property_def const& property) +{ + return property_managed_name(property.name); +} + inline std::string managed_part_name(attributes::part_def const& part) { std::vector names = utils::split(part.name, '_'); diff --git a/src/bin/eolian_mono/eolian_mono.cc b/src/bin/eolian_mono/eolian_mono.cc index 2e0e8918cb..db888816a2 100644 --- a/src/bin/eolian_mono/eolian_mono.cc +++ b/src/bin/eolian_mono/eolian_mono.cc @@ -138,11 +138,14 @@ run(options_type const& opts) throw std::runtime_error("Failed to generate file preamble"); } - auto context = efl::eolian::grammar::context_add_tag(eolian_mono::library_context{opts.dllimport, + auto lib_context = efl::eolian::grammar::context_add_tag(eolian_mono::library_context{opts.dllimport, opts.v_major, opts.v_minor, opts.references_map}, efl::eolian::grammar::context_null()); + + auto context = efl::eolian::grammar::context_add_tag(eolian_mono::eolian_state_context{opts.state}, lib_context); + EINA_ITERATOR_FOREACH(aliases, tp) { if (eolian_typedecl_type_get(tp) == EOLIAN_TYPEDECL_FUNCTION_POINTER) @@ -179,7 +182,8 @@ run(options_type const& opts) , enum_last; enum_iterator != enum_last; ++enum_iterator) { efl::eolian::grammar::attributes::enum_def enum_(&*enum_iterator, opts.unit); - if (!eolian_mono::enum_definition.generate(iterator, enum_, efl::eolian::grammar::context_null())) + auto enum_cxt = context_add_tag(class_context{class_context::enums}, context); + if (!eolian_mono::enum_definition.generate(iterator, enum_, enum_cxt)) { throw std::runtime_error("Failed to generate enum"); } diff --git a/src/lib/eolian_cxx/grammar/klass_def.hpp b/src/lib/eolian_cxx/grammar/klass_def.hpp index ce54f35dc1..008b5aa0e6 100644 --- a/src/lib/eolian_cxx/grammar/klass_def.hpp +++ b/src/lib/eolian_cxx/grammar/klass_def.hpp @@ -174,10 +174,11 @@ struct documentation_def std::string description; std::string since; std::vector desc_paragraphs; + std::string full_text; documentation_def() = default; - documentation_def(std::string summary, std::string description, std::string since) - : summary(summary), description(description), since(since) + documentation_def(std::string summary, std::string description, std::string since, std::string full_text) + : summary(summary), description(description), since(since), full_text(full_text) {} documentation_def(Eolian_Documentation const* eolian_doc) { @@ -188,15 +189,19 @@ struct documentation_def str = eolian_documentation_summary_get(eolian_doc); if (str) - summary = str; + full_text = summary = str; str = eolian_documentation_description_get(eolian_doc); - if (str) + if (str) { description = str; + full_text += "\n" + description; + } str = eolian_documentation_since_get(eolian_doc); - if (str) + if (str) { since = str; + full_text += "\n" + since; + } efl::eina::ptr_list l(eolian_documentation_string_split(description.c_str())); for (auto&& i : l) @@ -441,7 +446,6 @@ struct alias_def } documentation = ::eolian_typedecl_documentation_get(alias_obj); - } }; @@ -627,6 +631,7 @@ struct function_def { Eolian_Type const* r_type = ::eolian_function_return_type_get(function, type); name = ::eolian_function_name_get(function); + return_documentation = eolian_function_return_documentation_get(function, type); if(r_type) return_type.set(r_type, unit, EOLIAN_C_TYPE_RETURN); if(type == EOLIAN_METHOD || type == EOLIAN_FUNCTION_POINTER) @@ -660,6 +665,9 @@ struct function_def if(!r_type && type == EOLIAN_PROP_GET && values.size() == 1) { return_type = values[0].type; + if (return_documentation.summary.empty()) + return_documentation = values[0].documentation; + } else if(type == EOLIAN_PROP_GET) { @@ -696,8 +704,6 @@ struct function_def is_protected = eolian_function_scope_get(function, type) == EOLIAN_SCOPE_PROTECTED; is_static = eolian_function_is_class(function); - return_documentation = eolian_function_return_documentation_get(function, type); - Eolian_Implement const* implement = eolian_function_implement_get(function); if (!implement) return; @@ -823,7 +829,7 @@ struct property_def property_def() = default; property_def(Eolian_Function const *function, efl::eina::optional getter - , efl::eina::optional setter, Eolian_Unit const* unit) + , efl::eina::optional setter, Eolian_Unit const*) : getter(getter), setter(setter) { name = ::eolian_function_name_get(function);