From f5d01b4cc7ff4b19ffe83c44834132be1ff3c8d6 Mon Sep 17 00:00:00 2001 From: Jeongseok Lee Date: Thu, 16 Apr 2020 14:47:38 -0700 Subject: [PATCH 1/5] Add static method example -- failure case --- test/examples/02_class/class.h | 24 ++++++++ test/examples/02_class/class.py | 98 +++++++++++-------------------- test/examples/02_class/class.yaml | 6 +- 3 files changed, 61 insertions(+), 67 deletions(-) diff --git a/test/examples/02_class/class.h b/test/examples/02_class/class.h index 4f05e08..09e5d2d 100644 --- a/test/examples/02_class/class.h +++ b/test/examples/02_class/class.h @@ -99,4 +99,28 @@ class ClassInDetail } // namespace detail +namespace test1 +{ + +class Integer +{ +public: + Integer(int val) : m_val(val) + { + } + int add(int a) + { + return a + m_val; + } + static int add(int a, int b) + { + return a + b; + } + +private: + int m_val; +}; + +} // namespace test1 + } // namespace chimera_test diff --git a/test/examples/02_class/class.py b/test/examples/02_class/class.py index 114c435..d309636 100644 --- a/test/examples/02_class/class.py +++ b/test/examples/02_class/class.py @@ -10,92 +10,60 @@ import class_boost_python as boost -class TestFunction(unittest.TestCase): +class TestClass(unittest.TestCase): - def test_function_py11(self): - if not has_pybind11: - return - - dog = py11.Dog() - self.assertEqual(dog.type(), 'Dog') - self.assertEqual(dog.pure_virtual_type(), 'Dog') - self.assertEqual(py11.Dog.static_type(), 'Dog') - - husky = py11.Husky() - self.assertEqual(husky.type(), 'Husky') - self.assertEqual(husky.pure_virtual_type(), 'Husky') - - strong_husky = py11.StrongHusky() - self.assertEqual(strong_husky.type(), 'StrongHusky') - self.assertEqual(strong_husky.pure_virtual_type(), 'StrongHusky') - - default_args = py11.DefaultArguments() - self.assertEqual(default_args.add(), 3) - self.assertEqual(default_args.add(2), 4) - self.assertEqual(default_args.add(2, 3), 5) - - self.assertEqual(py11.StaticFields.m_static_readonly_type, 'static readonly type') - self.assertEqual(py11.StaticFields.m_static_readwrite_type, 'static readwrite type') - py11.StaticFields.m_static_readwrite_type = 'new type' - self.assertEqual(py11.StaticFields.m_static_readwrite_type, 'new type') - self.assertEqual(py11.StaticFields.static_type(), 'static type') - - non_pub_param_in_ctr = py11.NonPublicParamInConstructor('Herb') - self.assertEqual(non_pub_param_in_ctr.m_name, 'Herb') - - # Check if the main class and the nested class can be created w/o errors - mc = py11.MainClass() - nc = py11.MainClass.NestedClass() - - # MainClass shouldn't be a module - self.assertFalse(inspect.ismodule(py11.MainClass)) - self.assertTrue(inspect.isclass(py11.MainClass)) - self.assertTrue(inspect.isclass(py11.MainClass.NestedClass)) - - import class_pybind11 - self.assertFalse(hasattr(class_pybind11, 'detail')) - - def test_function_bp(self): - dog = boost.Dog() + def _test_class(self, binding): + dog = binding.Dog() self.assertEqual(dog.type(), 'Dog') self.assertEqual(dog.pure_virtual_type(), 'Dog') - self.assertEqual(boost.Dog.static_type(), 'Dog') + self.assertEqual(binding.Dog.static_type(), 'Dog') - husky = boost.Husky() + husky = binding.Husky() self.assertEqual(husky.type(), 'Husky') self.assertEqual(husky.pure_virtual_type(), 'Husky') - strong_husky = boost.StrongHusky() + strong_husky = binding.StrongHusky() self.assertEqual(strong_husky.type(), 'StrongHusky') self.assertEqual(strong_husky.pure_virtual_type(), 'StrongHusky') - default_args = boost.DefaultArguments() + default_args = binding.DefaultArguments() self.assertEqual(default_args.add(), 3) self.assertEqual(default_args.add(2), 4) self.assertEqual(default_args.add(2, 3), 5) - self.assertEqual(boost.StaticFields.m_static_readonly_type, 'static readonly type') - self.assertEqual(boost.StaticFields.m_static_readwrite_type, 'static readwrite type') + self.assertEqual(binding.StaticFields.m_static_readonly_type, 'static readonly type') + self.assertEqual(binding.StaticFields.m_static_readwrite_type, 'static readwrite type') # TODO(JS): Boost.Python doesn't work for readwrite static field (see: #194) - # boost.StaticFields.m_static_readwrite_type = 'new type' - # self.assertEqual(boost.StaticFields.m_static_readwrite_type, 'new type') - self.assertEqual(boost.StaticFields.static_type(), 'static type') + if has_pybind11 and binding is py11: + binding.StaticFields.m_static_readwrite_type = 'new type' + self.assertEqual(binding.StaticFields.m_static_readwrite_type, 'new type') + self.assertEqual(binding.StaticFields.static_type(), 'static type') - non_pub_param_in_ctr = boost.NonPublicParamInConstructor('Herb') + non_pub_param_in_ctr = binding.NonPublicParamInConstructor('Herb') self.assertEqual(non_pub_param_in_ctr.m_name, 'Herb') # Check if the main class and the nested class can be created w/o errors - mc = boost.MainClass() - nc = boost.MainClass.NestedClass() + mc = binding.MainClass() + nc = binding.MainClass.NestedClass() # MainClass shouldn't be a module - self.assertFalse(inspect.ismodule(boost.MainClass)) - self.assertTrue(inspect.isclass(boost.MainClass)) - self.assertTrue(inspect.isclass(boost.MainClass.NestedClass)) - - import class_boost_python - self.assertFalse(hasattr(class_boost_python, 'detail')) - + self.assertFalse(inspect.ismodule(binding.MainClass)) + self.assertTrue(inspect.isclass(binding.MainClass)) + self.assertTrue(inspect.isclass(binding.MainClass.NestedClass)) + + self.assertFalse(hasattr(binding, 'detail')) + + # Call static method + self.assertEqual(binding.test1.Integer.Add(1, 2), 3) + + # Call instance method + i = binding.test1.Integer(1) + self.assertEqual(i.add(2), 3) + + def test_class(self): + self._test_class(boost) + if has_pybind11: + self._test_class(py11) if __name__ == '__main__': unittest.main() diff --git a/test/examples/02_class/class.yaml b/test/examples/02_class/class.yaml index 477d231..a9943ac 100644 --- a/test/examples/02_class/class.yaml +++ b/test/examples/02_class/class.yaml @@ -2,7 +2,9 @@ arguments: - "-extra-arg" - "-I/usr/lib/clang/3.6/include" +options: + static_method_name_policy: "first_letter_capital" namespaces: - 'chimera_test': + "chimera_test": name: null # TODO: otherwise, import error - 'chimera_test::detail': null + "chimera_test::detail": null From da68af26c50fbd23a19d0df677d9f55d69806a55 Mon Sep 17 00:00:00 2001 From: Jeongseok Lee Date: Thu, 16 Apr 2020 14:48:09 -0700 Subject: [PATCH 2/5] Change static method if the name conflict with instance methods --- include/chimera/configuration.h | 17 ++++++- include/chimera/mstch.h | 6 +++ src/configuration.cpp | 26 +++++++++++ src/mstch.cpp | 82 +++++++++++++++++++++++++++++++-- 4 files changed, 126 insertions(+), 5 deletions(-) diff --git a/include/chimera/configuration.h b/include/chimera/configuration.h index d8d37bd..1cf331f 100644 --- a/include/chimera/configuration.h +++ b/include/chimera/configuration.h @@ -120,15 +120,29 @@ class Configuration class CompiledConfiguration { public: + enum class StaticMethodNamePolicy + { + NO_CHANGE, + FIRST_LETTER_CAPITAL, + CAPITAL, + // TODO: Add more policies such as prefix and suffix + }; + virtual ~CompiledConfiguration() = default; CompiledConfiguration(const CompiledConfiguration &) = delete; CompiledConfiguration &operator=(const CompiledConfiguration &) = delete; /** - * Return whether to treat unresolvable configuration as errors. + * Returns whether to treat unresolvable configuration as errors. */ bool GetStrict() const; + /** + * Returns policy for the case that a static method has a same name one of + * the instance method names in the same class. + */ + StaticMethodNamePolicy GetStaticMethodNamePolicy() const; + /** * Adds a namespace to an ordered set of traversed namespaces. * This set can later be rendered in a template. @@ -258,6 +272,7 @@ class CompiledConfiguration std::set binding_namespace_decls_; bool strict_; + StaticMethodNamePolicy static_method_name_policy_; friend class Configuration; }; diff --git a/include/chimera/mstch.h b/include/chimera/mstch.h index 7d85519..01498f5 100644 --- a/include/chimera/mstch.h +++ b/include/chimera/mstch.h @@ -307,13 +307,19 @@ class Method : public Function const clang::CXXMethodDecl *decl, const clang::CXXRecordDecl *class_decl = nullptr); + void setNameConflict(bool val); + bool isNameConflict() const; + + std::string nameAsString() override; ::mstch::node isConst(); + bool isStaticAsBool() const; ::mstch::node isStatic(); ::mstch::node isVirtual(); ::mstch::node isPureVirtual(); private: const clang::CXXMethodDecl *method_decl_; + bool name_conflict_; }; class Namespace : public ClangWrapper diff --git a/src/configuration.cpp b/src/configuration.cpp index b176163..ceec8d0 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -193,6 +193,26 @@ chimera::CompiledConfiguration::CompiledConfiguration( config_options_strict = strictNode.as(); } + // Parse 'options.static_method_name_policy' to set the static method name + // policy for the case a static method has the same name one of the instance + // methods in the same class. + static_method_name_policy_ = StaticMethodNamePolicy::NO_CHANGE; + const YAML::Node staticMethodNamePolicyNode = chimera::util::lookupYAMLNode( + configNode_, "options", "static_method_name_policy"); + if (staticMethodNamePolicyNode) + { + const std::string policy = staticMethodNamePolicyNode.as(); + if (policy == "first_letter_capital") + { + static_method_name_policy_ + = StaticMethodNamePolicy::FIRST_LETTER_CAPITAL; + } + else if (policy == "capital") + { + static_method_name_policy_ = StaticMethodNamePolicy::CAPITAL; + } + } + // Set the options.strict from one of the following sources in order of // priority: 1) CLI '-strict' setting (True if -strict passed, False // otherwise) 2) YAML configuration setting (True/False) 3) false by default @@ -478,6 +498,12 @@ bool chimera::CompiledConfiguration::GetStrict() const return strict_; } +chimera::CompiledConfiguration::StaticMethodNamePolicy +chimera::CompiledConfiguration::GetStaticMethodNamePolicy() const +{ + return static_method_name_policy_; +} + void chimera::CompiledConfiguration::AddTraversedNamespace( const clang::NamespaceDecl *decl) { diff --git a/src/mstch.cpp b/src/mstch.cpp index f1d2c1d..e1feda4 100644 --- a/src/mstch.cpp +++ b/src/mstch.cpp @@ -3,8 +3,10 @@ #include "chimera/util.h" #include "cling_utils_AST.h" +#include #include #include +#include #include #include @@ -479,8 +481,31 @@ ::mstch::node CXXRecord::methods() method_vector.back()->setLast(true); // Copy each template into the mstch template array. - for (auto method_template : method_vector) - method_templates.push_back(method_template); + for (const std::shared_ptr &method : method_vector) + method_templates.push_back(method); + + // Collect all the non-static method names + std::unordered_set non_static_method_names; + for (const std::shared_ptr &method : method_vector) + { + if (!method->isStaticAsBool()) + non_static_method_names.insert(method->nameAsString()); + } + + // Set each static member whether it has the same name of the instance + // methods in the same class, which is not allowed by Python. How to handle + // the name conflict is determined by the configuration (see also + // CompiledConfiguration::StaticMethodNamePolicy). + for (const std::shared_ptr &method : method_vector) + { + if (method->isStaticAsBool()) + { + auto it = non_static_method_names.find(method->nameAsString()); + if (it != non_static_method_names.end()) + method->setNameConflict(true); + } + } + return method_templates; } @@ -969,7 +994,9 @@ ::mstch::node Function::isTemplate() Method::Method(const ::chimera::CompiledConfiguration &config, const CXXMethodDecl *decl, const CXXRecordDecl *class_decl) - : Function(config, decl, class_decl), method_decl_(decl) + : Function(config, decl, class_decl) + , method_decl_(decl) + , name_conflict_(false) { register_methods(this, { {"is_const", &Method::isConst}, @@ -979,16 +1006,63 @@ Method::Method(const ::chimera::CompiledConfiguration &config, }); } +void Method::setNameConflict(bool val) +{ + name_conflict_ = val; +} + +bool Method::isNameConflict() const +{ + return name_conflict_; +} + +std::string Method::nameAsString() +{ + std::string original_name = Function::nameAsString(); + + // TODO: This shouldn't happen + if (original_name.empty()) + return original_name; + + if (isStaticAsBool() && name_conflict_) + { + switch (config_.GetStaticMethodNamePolicy()) + { + case CompiledConfiguration::StaticMethodNamePolicy::NO_CHANGE: + // Do nothing + break; + case CompiledConfiguration::StaticMethodNamePolicy:: + FIRST_LETTER_CAPITAL: + std::transform(original_name.begin(), original_name.begin() + 1, + original_name.begin(), ::toupper); + break; + case CompiledConfiguration::StaticMethodNamePolicy::CAPITAL: + std::transform(original_name.begin(), original_name.end(), + original_name.begin(), ::toupper); + break; + default: + break; + } + } + + return original_name; +} + ::mstch::node Method::isConst() { return method_decl_->isConst(); } -::mstch::node Method::isStatic() +bool Method::isStaticAsBool() const { return method_decl_->isStatic(); } +::mstch::node Method::isStatic() +{ + return isStaticAsBool(); +} + ::mstch::node Method::isVirtual() { return method_decl_->isVirtual(); From 8272f0c93e76cfed55953e1dd58b6bb3bb12b18a Mon Sep 17 00:00:00 2001 From: Jeongseok Lee Date: Thu, 16 Apr 2020 22:24:13 -0700 Subject: [PATCH 3/5] Fix static method name policy was not applied to overloaded functions --- include/chimera/mstch.h | 7 +++++-- src/mstch.cpp | 22 ++++++++++++++++++++++ test/examples/02_class/class.h | 4 ++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/chimera/mstch.h b/include/chimera/mstch.h index 01498f5..c3fd633 100644 --- a/include/chimera/mstch.h +++ b/include/chimera/mstch.h @@ -281,7 +281,7 @@ class Function : public ClangWrapper, const int argument_limit = -1); ::mstch::node type(); - ::mstch::node overloads(); + virtual ::mstch::node overloads(); ::mstch::node params(); ::mstch::node returnType(); ::mstch::node returnValuePolicy(); @@ -295,8 +295,10 @@ class Function : public ClangWrapper, ::mstch::node call(); ::mstch::node qualifiedCall(); -private: +protected: const clang::CXXRecordDecl *class_decl_; + +private: const int argument_limit_; }; @@ -310,6 +312,7 @@ class Method : public Function void setNameConflict(bool val); bool isNameConflict() const; + ::mstch::node overloads() override; std::string nameAsString() override; ::mstch::node isConst(); bool isStaticAsBool() const; diff --git a/src/mstch.cpp b/src/mstch.cpp index e1feda4..ca2aa67 100644 --- a/src/mstch.cpp +++ b/src/mstch.cpp @@ -1016,6 +1016,28 @@ bool Method::isNameConflict() const return name_conflict_; } +::mstch::node Method::overloads() +{ + ::mstch::array overloads; + + // Create a list of wrappers that re-wrap this function with a subset of + // the full set of arguments (i.e. omitting some of the default arguments). + auto arg_range = chimera::util::getFunctionArgumentRange(decl_); + for (unsigned n_args = arg_range.first; n_args < arg_range.second; ++n_args) + { + auto method + = std::make_shared(config_, method_decl_, class_decl_); + method->setNameConflict(name_conflict_); + overloads.push_back(method); + } + + // Add this function to its own list of overloads. + // It can be distinguished from other copies because `uses_defaults=false`. + overloads.push_back(shared_from_this()); + + return overloads; +} + std::string Method::nameAsString() { std::string original_name = Function::nameAsString(); diff --git a/test/examples/02_class/class.h b/test/examples/02_class/class.h index 09e5d2d..476086d 100644 --- a/test/examples/02_class/class.h +++ b/test/examples/02_class/class.h @@ -112,9 +112,9 @@ class Integer { return a + m_val; } - static int add(int a, int b) + static int add(int a, int b, int c = 0) { - return a + b; + return a + b + c; } private: From 7cc5d799af133d3c1bd5ae71cbe5eb21456fbb67 Mon Sep 17 00:00:00 2001 From: Jeongseok Lee Date: Thu, 16 Apr 2020 22:37:20 -0700 Subject: [PATCH 4/5] Fix generating overloaded functions --- include/chimera/mstch.h | 3 ++- src/mstch.cpp | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/chimera/mstch.h b/include/chimera/mstch.h index c3fd633..a966b9a 100644 --- a/include/chimera/mstch.h +++ b/include/chimera/mstch.h @@ -307,7 +307,8 @@ class Method : public Function public: Method(const ::chimera::CompiledConfiguration &config, const clang::CXXMethodDecl *decl, - const clang::CXXRecordDecl *class_decl = nullptr); + const clang::CXXRecordDecl *class_decl = nullptr, + const int argument_limit = -1); void setNameConflict(bool val); bool isNameConflict() const; diff --git a/src/mstch.cpp b/src/mstch.cpp index ca2aa67..ec0fa3e 100644 --- a/src/mstch.cpp +++ b/src/mstch.cpp @@ -993,8 +993,9 @@ ::mstch::node Function::isTemplate() } Method::Method(const ::chimera::CompiledConfiguration &config, - const CXXMethodDecl *decl, const CXXRecordDecl *class_decl) - : Function(config, decl, class_decl) + const CXXMethodDecl *decl, const CXXRecordDecl *class_decl, + const int argument_limit) + : Function(config, decl, class_decl, argument_limit) , method_decl_(decl) , name_conflict_(false) { @@ -1025,8 +1026,8 @@ ::mstch::node Method::overloads() auto arg_range = chimera::util::getFunctionArgumentRange(decl_); for (unsigned n_args = arg_range.first; n_args < arg_range.second; ++n_args) { - auto method - = std::make_shared(config_, method_decl_, class_decl_); + auto method = std::make_shared(config_, method_decl_, + class_decl_, n_args); method->setNameConflict(name_conflict_); overloads.push_back(method); } From a735c6641657269bf80b65b798b2fa69c4837eee Mon Sep 17 00:00:00 2001 From: Jeongseok Lee Date: Sun, 26 Apr 2020 11:01:21 -0700 Subject: [PATCH 5/5] Update name change policies --- CMakeLists.txt | 6 +-- include/chimera/configuration.h | 12 ++++- include/chimera/util.h | 32 +++++++++++ src/configuration.cpp | 43 +++++++++++---- src/mstch.cpp | 17 +++--- src/util.cpp | 89 +++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/examples/02_class/class.yaml | 2 +- test/test_util.cpp | 49 +++++++++++++++++ 9 files changed, 229 insertions(+), 22 deletions(-) create mode 100644 test/test_util.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dbf2104..c813f8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,13 +119,13 @@ add_library(libchimera ) target_compile_options(libchimera PUBLIC "-std=c++11") target_link_libraries(libchimera - PRIVATE + PUBLIC ${YAMLCPP_LIBRARIES} ${CLANG_LIBS} ${llvm_libs} ) -target_link_libraries(libchimera PRIVATE mstch cling_utils) -target_link_libraries(libchimera PRIVATE chimera_bindings) +target_link_libraries(libchimera PUBLIC mstch cling_utils) +target_link_libraries(libchimera PUBLIC chimera_bindings) target_compile_definitions(libchimera PUBLIC CHIMERA_MAJOR_VERSION=${CHIMERA_MAJOR_VERSION} diff --git a/include/chimera/configuration.h b/include/chimera/configuration.h index 1cf331f..c0e0f8d 100644 --- a/include/chimera/configuration.h +++ b/include/chimera/configuration.h @@ -123,8 +123,16 @@ class CompiledConfiguration enum class StaticMethodNamePolicy { NO_CHANGE, - FIRST_LETTER_CAPITAL, - CAPITAL, + // Cast all the letters to upper case + TO_UPPER, + // Cast all the letters to lower case + TO_LOWER, + // Cast the first letter to upper case and cast all the letters followed + // by underscore to upper cases removing the underscores + TO_PASCAL, + // Cast the first letter to lower case and cast all the letters followed + // by underscore to upper cases removing the underscores + TO_CAMEL, // TODO: Add more policies such as prefix and suffix }; diff --git a/include/chimera/util.h b/include/chimera/util.h index afaa652..bef956a 100644 --- a/include/chimera/util.h +++ b/include/chimera/util.h @@ -326,6 +326,38 @@ std::string trimLeft(std::string s, const char *t = " \t\n\r\f\v"); */ std::string trim(std::string s, const char *t = " \t\n\r\f\v"); +/** + * Casts all the letters to upper case + */ +std::string toUpper(const std::string &str, int n = -1); + +/** + * Casts all the letters to lower case + */ +std::string toLower(const std::string &str, int n = -1); + +/** + * Casts the first letter to upper case and casts all the letters followed by + * underscore to upper cases removing the underscores + */ +std::string toPascal(const std::string &str); + +/** + * Casts the first letter to lower case and casts all the letters followed by + * underscore to upper cases removing the underscores + */ +std::string toCamel(const std::string &str); + +/** + * Splits string into a list of tokens. + * + * @param[in] str The original string to be split. + * @param[in] delimiter The string delimiter, which can be more than one + * character. + */ +std::vector split(const std::string &str, + const std::string &delimiter = " "); + /** * Returns true if a string starts with a prefix, otherwise false. */ diff --git a/src/configuration.cpp b/src/configuration.cpp index ceec8d0..7c438e6 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -202,14 +202,39 @@ chimera::CompiledConfiguration::CompiledConfiguration( if (staticMethodNamePolicyNode) { const std::string policy = staticMethodNamePolicyNode.as(); - if (policy == "first_letter_capital") + if (policy == "to_upper") { - static_method_name_policy_ - = StaticMethodNamePolicy::FIRST_LETTER_CAPITAL; + static_method_name_policy_ = StaticMethodNamePolicy::TO_UPPER; } - else if (policy == "capital") + else if (policy == "to_lower") { - static_method_name_policy_ = StaticMethodNamePolicy::CAPITAL; + static_method_name_policy_ = StaticMethodNamePolicy::TO_UPPER; + } + else if (policy == "to_pascal") + { + static_method_name_policy_ = StaticMethodNamePolicy::TO_PASCAL; + } + else if (policy == "to_camel") + { + static_method_name_policy_ = StaticMethodNamePolicy::TO_CAMEL; + } + else + { + if (GetStrict()) + { + throw std::runtime_error( + "Invalid value for 'static_method_name_policy': '" + policy + + "'. Choose one of following: 'to_upper', 'to_lower', 'to_camel', 'to_pascal'"); + } + else + { + std::cerr + << "Invalid value for 'static_method_name_policy': '" + << policy + << "'. Choose one of following: 'no_change', 'to_upper', " + << "'to_lower', 'to_camel', 'to_pascal'. Defaulting to " + << "'no_change'." << std::endl; + } } } @@ -288,7 +313,7 @@ chimera::CompiledConfiguration::CompiledConfiguration( } else { - std::cout << "Warning: Skipped namespace namespace '" + std::cerr << "Warning: Skipped namespace namespace '" << ns_str << "' because it's unable to resolve " << "the namespace." << std::endl; @@ -344,7 +369,7 @@ chimera::CompiledConfiguration::CompiledConfiguration( } else { - std::cout + std::cerr << "Warning: Skipped the configuration for class '" << decl_str << "' becuase it's " << "unable to resolve the class declaration." @@ -383,7 +408,7 @@ chimera::CompiledConfiguration::CompiledConfiguration( } else { - std::cout + std::cerr << "Warning: Skipped the configuration for " << "function '" << decl_str << "' becuase it's " << "unable to resolve the function declaration." @@ -422,7 +447,7 @@ chimera::CompiledConfiguration::CompiledConfiguration( } else { - std::cout << "Warning: Skipped the configuration for " + std::cerr << "Warning: Skipped the configuration for " << "type '" << type_str << "' becuase it's " << "unable to resolve the type." << std::endl; continue; diff --git a/src/mstch.cpp b/src/mstch.cpp index ec0fa3e..ed96486 100644 --- a/src/mstch.cpp +++ b/src/mstch.cpp @@ -1054,14 +1054,17 @@ std::string Method::nameAsString() case CompiledConfiguration::StaticMethodNamePolicy::NO_CHANGE: // Do nothing break; - case CompiledConfiguration::StaticMethodNamePolicy:: - FIRST_LETTER_CAPITAL: - std::transform(original_name.begin(), original_name.begin() + 1, - original_name.begin(), ::toupper); + case CompiledConfiguration::StaticMethodNamePolicy::TO_UPPER: + original_name = chimera::util::toUpper(original_name); break; - case CompiledConfiguration::StaticMethodNamePolicy::CAPITAL: - std::transform(original_name.begin(), original_name.end(), - original_name.begin(), ::toupper); + case CompiledConfiguration::StaticMethodNamePolicy::TO_LOWER: + original_name = chimera::util::toLower(original_name); + break; + case CompiledConfiguration::StaticMethodNamePolicy::TO_PASCAL: + original_name = chimera::util::toPascal(original_name); + break; + case CompiledConfiguration::StaticMethodNamePolicy::TO_CAMEL: + original_name = chimera::util::toCamel(original_name); break; default: break; diff --git a/src/util.cpp b/src/util.cpp index bd32732..aa0da79 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -955,6 +955,95 @@ std::string trim(std::string s, const char *t) return trimLeft(trimRight(s, t), t); } +std::string toUpper(const std::string &str, int n) +{ + std::string casted = str; + if (n < 0 || static_cast(n) >= casted.length()) + { + std::transform(casted.begin(), casted.end(), casted.begin(), ::toupper); + } + else + { + std::transform(casted.begin(), casted.begin() + n, casted.begin(), + ::toupper); + } + return casted; +} + +std::string toLower(const std::string &str, int n) +{ + std::string casted = str; + if (n < 0 || static_cast(n) >= casted.length()) + { + std::transform(casted.begin(), casted.end(), casted.begin(), ::tolower); + } + else + { + std::transform(casted.begin(), casted.begin() + n, casted.begin(), + ::tolower); + } + return casted; +} + +std::string toPascal(const std::string &str) +{ + auto tokens = split(str, "_"); + for (auto &token : tokens) + token = toUpper(token, 1); + + std::string casted; + for (const auto &token : tokens) + casted += token; + + return casted; +} + +std::string toCamel(const std::string &str) +{ + auto tokens = split(str, "_"); + + bool first_token_found = false; + for (auto &token : tokens) + { + if (!first_token_found) + { + if (token.empty()) + continue; + + token = toLower(token, 1); + first_token_found = true; + continue; + } + + token = toUpper(token, 1); + } + + std::string casted; + for (const auto &token : tokens) + casted += token; + + return casted; +} + +std::vector split(const std::string &str, + const std::string &delimiter) +{ + std::size_t pos_start = 0; + std::size_t pos_end, delim_len = delimiter.length(); + std::string token; + std::vector tokens; + + while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos) + { + token = str.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + tokens.push_back(token); + } + + tokens.push_back(str.substr(pos_start)); + return tokens; +} + bool startsWith(const std::string &str, const std::string &prefix) { return ((prefix.size() <= str.size()) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 013e791..dd1567d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -70,6 +70,7 @@ endfunction() # Add tests #=============================================================================== chimera_add_test(test_emulator) +chimera_add_test(test_util) # Add custom target to build all the tests as a single target get_property(chimera_cpp_tests GLOBAL PROPERTY CHIMERA_CPP_TESTS) diff --git a/test/examples/02_class/class.yaml b/test/examples/02_class/class.yaml index a9943ac..59104da 100644 --- a/test/examples/02_class/class.yaml +++ b/test/examples/02_class/class.yaml @@ -3,7 +3,7 @@ arguments: - "-extra-arg" - "-I/usr/lib/clang/3.6/include" options: - static_method_name_policy: "first_letter_capital" + static_method_name_policy: "to_pascal" namespaces: "chimera_test": name: null # TODO: otherwise, import error diff --git a/test/test_util.cpp b/test/test_util.cpp new file mode 100644 index 0000000..a420632 --- /dev/null +++ b/test/test_util.cpp @@ -0,0 +1,49 @@ +#include +#include "chimera/util.h" + +using namespace chimera; + +//============================================================================== +TEST(Util, StringCast) +{ + // Test toUpper + EXPECT_EQ(util::toUpper("Illusory_or_Impossible"), + "ILLUSORY_OR_IMPOSSIBLE"); + EXPECT_EQ(util::toUpper("Illusory_or_Impossible", -1), + "ILLUSORY_OR_IMPOSSIBLE"); + EXPECT_EQ(util::toUpper("Illusory_or_Impossible", 0), + "Illusory_or_Impossible"); + EXPECT_EQ(util::toUpper("Illusory_or_Impossible", 2), + "ILlusory_or_Impossible"); + + // Test toLower + EXPECT_EQ(util::toLower("Illusory_or_Impossible"), + "illusory_or_impossible"); + EXPECT_EQ(util::toLower("Illusory_or_Impossible", -1), + "illusory_or_impossible"); + EXPECT_EQ(util::toLower("Illusory_or_Impossible", 0), + "Illusory_or_Impossible"); + EXPECT_EQ(util::toLower("Illusory_or_Impossible", 2), + "illusory_or_Impossible"); + + // Test toPascal + EXPECT_EQ(util::toPascal("illusory_or_impossible"), "IllusoryOrImpossible"); + EXPECT_EQ(util::toPascal("illusoryOrImpossible"), "IllusoryOrImpossible"); + EXPECT_EQ(util::toPascal("IllusoryOrImpossible"), "IllusoryOrImpossible"); + EXPECT_EQ(util::toPascal("illusory_or__impossible"), + "IllusoryOrImpossible"); + EXPECT_EQ(util::toPascal("illusory_or_impossible___"), + "IllusoryOrImpossible"); + EXPECT_EQ(util::toPascal("____illusory_or_impossible"), + "IllusoryOrImpossible"); + + // Test toCamel + EXPECT_EQ(util::toCamel("illusory_or_impossible"), "illusoryOrImpossible"); + EXPECT_EQ(util::toCamel("illusoryOrImpossible"), "illusoryOrImpossible"); + EXPECT_EQ(util::toCamel("IllusoryOrImpossible"), "illusoryOrImpossible"); + EXPECT_EQ(util::toCamel("illusory_or__impossible"), "illusoryOrImpossible"); + EXPECT_EQ(util::toCamel("illusory_or_impossible___"), + "illusoryOrImpossible"); + EXPECT_EQ(util::toCamel("____illusory_or_impossible"), + "illusoryOrImpossible"); +}