├── .clang-format ├── .clang-tidy.in ├── .github └── workflows │ ├── unix.yml │ └── windows.yml ├── .gitignore ├── .pkg ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── buildcache.cmake ├── clang-tidy.cmake └── pkg.cmake ├── express ├── exe │ └── express_gen.cc ├── include │ └── express │ │ ├── exp_struct_gen.h │ │ └── parse_exp.h ├── src │ ├── exp_struct_gen.cc │ └── parse_exp.cc └── test │ ├── exp_test.cc │ ├── ifc23.txt │ └── main.cc ├── logo.png ├── step ├── include │ └── step │ │ ├── assign_entity_ptr_to_select.h │ │ ├── exp_logical.h │ │ ├── has_data.h │ │ ├── id_t.h │ │ ├── is_collection.h │ │ ├── model.h │ │ ├── parse_lines.h │ │ ├── parse_step.h │ │ ├── resolve.h │ │ ├── root_entity.h │ │ ├── selective_entity_parser.h │ │ ├── split_line.h │ │ └── write.h ├── src │ ├── exp_logical.cc │ ├── id_t.cc │ ├── root_entity.cc │ ├── split_line.cc │ └── write.cc └── test │ ├── entry_parser_test.cc │ ├── main.cc │ ├── parse_basic_datatypes_test.cc │ ├── parse_ifc_test.cc │ └── writer_test.cc └── tools ├── buildcache-clang-tidy.lua └── test_dir.h.in /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 2 3 | Language: Cpp 4 | DerivePointerAlignment: false 5 | PointerAlignment: Left 6 | AccessModifierOffset: -2 7 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 8 | AlignTrailingComments: false 9 | KeepEmptyLinesAtTheStartOfBlocks: true 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AlwaysBreakTemplateDeclarations: true 12 | SpacesBeforeTrailingComments: 2 13 | IncludeBlocks: Preserve 14 | IncludeCategories: 15 | - Regex: '^<.*\.h>' 16 | Priority: 1 17 | - Regex: '^' 41 | Priority: 1 42 | - Regex: '^:Debug>") 5 | 6 | if (MSVC) 7 | # PDB debug information is not supported by buildcache. 8 | string(REPLACE "/Zi" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 9 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") 10 | string(REPLACE "/Zi" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 11 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") 12 | endif () 13 | 14 | include(cmake/buildcache.cmake) 15 | include(cmake/pkg.cmake) 16 | 17 | option(EXPRESS2CPP_LINT "Run clang-tidy with the compiler." OFF) 18 | if (EXPRESS2CPP_LINT) 19 | include(cmake/clang-tidy.cmake) 20 | endif () 21 | 22 | configure_file( 23 | ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_dir.h.in 24 | ${CMAKE_BINARY_DIR}/generated/test_dir.h 25 | ) 26 | add_library(test-dir INTERFACE) 27 | target_include_directories(test-dir INTERFACE ${CMAKE_BINARY_DIR}/generated) 28 | 29 | file(GLOB express-files express/src/*.cc) 30 | add_library(express ${express-files}) 31 | target_include_directories(express PUBLIC express/include) 32 | target_link_libraries(express boost-filesystem boost cista utl) 33 | target_compile_features(express PUBLIC cxx_std_17) 34 | 35 | file(GLOB express-gen-files express/exe/express_gen.cc) 36 | add_executable(express-gen ${express-gen-files}) 37 | target_link_libraries(express-gen boost cista express) 38 | target_compile_features(express-gen PUBLIC cxx_std_17) 39 | 40 | file(GLOB express-test-files express/test/*.cc) 41 | add_executable(express-test ${express-test-files}) 42 | target_include_directories(express-test PUBLIC include) 43 | target_link_libraries(express-test doctest test-dir express) 44 | target_compile_features(express-test PUBLIC cxx_std_17) 45 | 46 | file(GLOB step-files step/src/*.cc) 47 | add_library(step ${step-files}) 48 | target_include_directories(step PUBLIC step/include) 49 | target_link_libraries(step boost cista utl) 50 | target_compile_features(step PUBLIC cxx_std_17) 51 | 52 | function(express2cpp express-file lib) 53 | add_custom_command( 54 | COMMAND express-gen ${CMAKE_CURRENT_SOURCE_DIR}/${express-file} ${CMAKE_CURRENT_BINARY_DIR}/${lib} 55 | DEPENDS express-gen 56 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lib}/${lib}.cc 57 | ) 58 | add_library(${lib} ${CMAKE_CURRENT_BINARY_DIR}/${lib}/${lib}.cc) 59 | if (MSVC) 60 | target_compile_options(${lib} PRIVATE /bigobj) 61 | endif () 62 | target_link_libraries(${lib} step utl cista boost) 63 | target_include_directories(${lib} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/${lib}/include ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/include) 64 | target_compile_features(${lib} PUBLIC cxx_std_17) 65 | set_target_properties(${lib} PROPERTIES CXX_CLANG_TIDY "") 66 | endfunction() 67 | 68 | express2cpp(express/test/ifc23.txt ifc23) 69 | file(GLOB step-test-files step/test/*.cc) 70 | add_executable(step-test ${step-test-files}) 71 | target_include_directories(step-test PUBLIC include) 72 | target_link_libraries(step-test doctest test-dir step ifc23) 73 | target_compile_features(step-test PUBLIC cxx_std_17) 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Baumhaus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](logo.png) 2 | 3 | ![Windows Build](https://github.com/baumhaus-project/express2cpp/workflows/Windows%20Build/badge.svg) 4 | ![Unix Build](https://github.com/baumhaus-project/express2cpp/workflows/Unix%20Build/badge.svg) 5 | 6 | # Usage 7 | 8 | CMakeLists.txt 9 | ```cmake 10 | express2cpp(./path/to/IFC23.EXP ifc2x3) 11 | add_executable(exe main.cc) 12 | target_link_libraries(exe ifc2x3) 13 | ``` 14 | 15 | main.cc 16 | ```cpp 17 | #include "IFC2X3/IfcProduct.h" 18 | #include "IFC2X3/parser.h" 19 | 20 | int main() { 21 | auto model = IFC2X3::parse(ifc_input); 22 | model.get_entity(1337); 23 | } 24 | ``` 25 | 26 | # Supported Targets 27 | 28 | - GCC 10.2 (10.1 not working!) 29 | - Clang 11, 12 (previous versions not tested) 30 | - Apple Clang 12 (previous versions not tested) 31 | - MSVC Latest (previous versions not tested) 32 | -------------------------------------------------------------------------------- /cmake/buildcache.cmake: -------------------------------------------------------------------------------- 1 | 2 | option(NO_BUILDCACHE "Disable build caching using buildcache" Off) 3 | 4 | set(buildcache-bin ${CMAKE_CURRENT_BINARY_DIR}/bin/buildcache) 5 | get_property(rule-launch-set GLOBAL PROPERTY RULE_LAUNCH_COMPILE SET) 6 | if (NO_BUILDCACHE) 7 | message(STATUS "NO_BUILDCACHE set, buildcache disabled") 8 | elseif (rule-launch-set) 9 | message(STATUS "Global property RULE_LAUNCH_COMPILE already set - skipping buildcache") 10 | else () 11 | find_program(buildcache_program buildcache HINTS ${CMAKE_CURRENT_BINARY_DIR}/bin) 12 | if (buildcache_program) 13 | message(STATUS "Using buildcache: ${buildcache_program}") 14 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${buildcache_program}") 15 | else () 16 | message(STATUS "buildcache not found - downloading") 17 | if (APPLE) 18 | set(buildcache-archive "buildcache-macos.zip") 19 | elseif (UNIX) 20 | set(buildcache-archive "buildcache-linux.zip") 21 | elseif (WIN32) 22 | set(buildcache-archive "buildcache-win-msvc.zip") 23 | else () 24 | message(FATAL "Error: NO_BUILDCACHE was not set but buildcache was not in path and system OS detection failed") 25 | endif () 26 | 27 | set(buildcache-url "https://github.com/mbitsnbites/buildcache/releases/download/v0.19.1/${buildcache-archive}") 28 | message(STATUS "Downloading buildcache binary from ${buildcache-url}") 29 | file(DOWNLOAD "${buildcache-url}" ${CMAKE_CURRENT_BINARY_DIR}/${buildcache-archive}) 30 | execute_process( 31 | COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_CURRENT_BINARY_DIR}/${buildcache-archive} 32 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 33 | message(STATUS "using buildcache: ${buildcache-bin}") 34 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${buildcache-bin}) 35 | endif () 36 | endif () -------------------------------------------------------------------------------- /cmake/clang-tidy.cmake: -------------------------------------------------------------------------------- 1 | if(CMake_SOURCE_DIR STREQUAL CMake_BINARY_DIR) 2 | message(FATAL_ERROR "CMake_RUN_CLANG_TIDY requires an out-of-source build!") 3 | endif() 4 | 5 | file(RELATIVE_PATH RELATIVE_SOURCE_DIR ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) 6 | 7 | find_program(CLANG_TIDY_COMMAND NAMES clang-tidy clang-tidy-11) 8 | if(NOT CLANG_TIDY_COMMAND) 9 | message(FATAL_ERROR "CMake_RUN_CLANG_TIDY is ON but clang-tidy is not found!") 10 | endif() 11 | set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}") 12 | 13 | file(SHA1 ${CMAKE_CURRENT_SOURCE_DIR}/.clang-tidy.in clang_tidy_sha1) 14 | set(CLANG_TIDY_DEFINITIONS "CLANG_TIDY_SHA1=${clang_tidy_sha1}") 15 | unset(clang_tidy_sha1) 16 | 17 | configure_file(.clang-tidy.in ${CMAKE_CURRENT_SOURCE_DIR}/.clang-tidy) -------------------------------------------------------------------------------- /cmake/pkg.cmake: -------------------------------------------------------------------------------- 1 | set(pkg-bin "${CMAKE_BINARY_DIR}/dl/pkg") 2 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 3 | set(pkg-url "pkg") 4 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") 5 | set(pkg-url "pkg.exe") 6 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 7 | set(pkg-url "pkgosx") 8 | else() 9 | message(STATUS "Not downloading pkg tool. Using pkg from PATH.") 10 | set(pkg-bin "pkg") 11 | endif() 12 | 13 | if (pkg-url) 14 | if (NOT EXISTS ${pkg-bin}) 15 | message(STATUS "Downloading pkg binary from https://github.com/motis-project/pkg/releases/latest/download/${pkg-url}") 16 | file(DOWNLOAD "https://github.com/motis-project/pkg/releases/latest/download/${pkg-url}" ${pkg-bin}) 17 | if (UNIX) 18 | execute_process(COMMAND chmod +x ${pkg-bin}) 19 | endif() 20 | else() 21 | message(STATUS "Pkg binary located in project.") 22 | endif() 23 | endif() 24 | 25 | message(STATUS "${pkg-bin} -l -h -f") 26 | execute_process( 27 | COMMAND ${pkg-bin} -l -h -f 28 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 29 | ) 30 | 31 | if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/deps") 32 | add_subdirectory(deps) 33 | endif() 34 | 35 | set_property( 36 | DIRECTORY 37 | APPEND 38 | PROPERTY CMAKE_CONFIGURE_DEPENDS 39 | "${CMAKE_CURRENT_SOURCE_DIR}/.pkg" 40 | ) 41 | -------------------------------------------------------------------------------- /express/exe/express_gen.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "boost/algorithm/string.hpp" 4 | #include "boost/filesystem.hpp" 5 | 6 | #include "cista/mmap.h" 7 | 8 | #include "express/exp_struct_gen.h" 9 | #include "express/parse_exp.h" 10 | 11 | namespace fs = boost::filesystem; 12 | 13 | int main(int argc, char** argv) { 14 | if (argc < 3) { 15 | std::cout << "usage: " << argv[0] << " EXPRESS_FILE TARGET_DIR\n"; 16 | return 1; 17 | } 18 | 19 | auto const root = fs::path{argv[2]}; 20 | fs::create_directories(root); 21 | if (!fs::is_regular_file(argv[1])) { 22 | std::cout << argv[1] << " has to be a file\n"; 23 | return 1; 24 | } 25 | 26 | auto const expr = cista::mmap{argv[1], cista::mmap::protection::READ}; 27 | auto const schema = express::parse(std::string_view{ 28 | reinterpret_cast(expr.data()), expr.size()}); 29 | 30 | auto const header_path = root / "include" / schema.name_; 31 | 32 | fs::create_directories(header_path); 33 | auto source_out = std::ofstream{ 34 | (root / (root.stem().generic_string() + ".cc")).generic_string().c_str()}; 35 | source_out << "#include \"step/id_t.h\"\n"; 36 | for (auto const& t : schema.types_) { 37 | auto header_out = std::ofstream{ 38 | (header_path / (t.name_ + ".h")).generic_string().c_str()}; 39 | express::generate_header(header_out, schema, t); 40 | express::generate_source(source_out, schema, t); 41 | } 42 | 43 | source_out << "\n\n" 44 | "#include \"" 45 | << schema.name_ << "/" 46 | << "parser.h\"\n" 47 | "#include \"step/root_entity.h\"\n" 48 | "#include \"step/parse_lines.h\"\n" 49 | "\n" 50 | << "namespace " << schema.name_ << "{\n" 51 | << "\n" 52 | "struct full_parser {\n" 53 | " std::optional parse(\n" 54 | " utl::cstr type_name,\n" 55 | " utl::cstr rest) const {\n" 56 | " switch (cista::hash(type_name.view())) {\n"; 57 | for (auto const& t : schema.types_) { 58 | if (t.data_type_ == express::data_type::ENTITY) { 59 | source_out << " case " 60 | << cista::hash(boost::to_upper_copy(t.name_)) 61 | << "U: { auto v = std::make_unique<" << t.name_ 62 | << ">(); parse_step(rest, *v); return " 63 | "std::unique_ptr(v.release()); }\n"; 64 | } 65 | } 66 | source_out 67 | << " default: return std::nullopt;\n" 68 | " }\n" 69 | " }\n" 70 | "};\n" 71 | "\n" 72 | "step::model parse(step::selective_entity_parser& p, utl::cstr s) {\n" 73 | " return step::parse_lines(p, s);\n" 74 | "}\n" 75 | "\n" 76 | "step::model parse(utl::cstr s) {\n" 77 | " return step::parse_lines(full_parser{}, s);\n" 78 | "}\n" 79 | "\n" 80 | "} // namespace " 81 | << schema.name_ << "\n"; 82 | 83 | auto types_header_out = 84 | std::ofstream{(header_path / ("parser.h")).generic_string().c_str()}; 85 | types_header_out 86 | << "#pragma once\n\n" 87 | << "#include \"step/model.h\"\n" 88 | << "#include \"step/selective_entity_parser.h\"\n\n" 89 | << "namespace " << schema.name_ << " {\n" 90 | << "\n" 91 | << "step::model parse(utl::cstr);\n" 92 | "\n" 93 | << "step::model parse(step::selective_entity_parser&, utl::cstr);\n" 94 | "\n" 95 | << "template \n" 96 | "step::model parse(utl::cstr s) {\n" 97 | " step::selective_entity_parser p;\n" 98 | " p.register_parsers();\n" 99 | " return parse(p, s);\n" 100 | "}\n" 101 | "\n" 102 | << "} // namespace " << schema.name_; 103 | } -------------------------------------------------------------------------------- /express/include/express/exp_struct_gen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "express/parse_exp.h" 6 | 7 | namespace express { 8 | 9 | void generate_header(std::ostream&, schema const&, type const&); 10 | void generate_source(std::ostream&, schema const&, type const&); 11 | 12 | } // namespace express -------------------------------------------------------------------------------- /express/include/express/parse_exp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "boost/variant.hpp" 11 | 12 | namespace express { 13 | 14 | enum class data_type { 15 | UNKOWN, 16 | BOOL, 17 | LOGICAL, 18 | REAL, 19 | NUMBER, 20 | STRING, 21 | INTEGER, 22 | ENTITY, 23 | ENUM, 24 | SELECT, 25 | ALIAS, 26 | BINARY 27 | }; 28 | constexpr char const* data_type_str[] = { 29 | "UNKOWN", "BOOL", "LOGICAL", "REAL", "NUMBER", "STRING", 30 | "INTEGER", "ENTITY", "ENUM", "SELECT", "ALIAS", "BINARY"}; 31 | 32 | struct parser_exception : public std::exception { 33 | parser_exception(char const* from, char const* to) : from_{from}, to_{to} {} 34 | char const *from_, *to_; 35 | }; 36 | 37 | struct list; 38 | struct schema; 39 | 40 | struct type_name { 41 | std::string name_; 42 | }; 43 | 44 | using member_type = boost::variant>; 45 | 46 | struct list { 47 | unsigned min_{0}, max_{std::numeric_limits::max()}; 48 | member_type m_; 49 | }; 50 | 51 | struct member { 52 | bool is_list(schema const&) const; 53 | std::string const& get_type_name() const; 54 | 55 | std::string name_; 56 | member_type type_; 57 | bool optional_; 58 | }; 59 | 60 | struct type { 61 | std::string name_; 62 | data_type data_type_{data_type::UNKOWN}; 63 | std::vector details_; 64 | std::string subtype_of_; 65 | std::vector members_; 66 | bool list_{false}; 67 | unsigned min_size_{0}, max_size_{std::numeric_limits::max()}; 68 | std::string alias_; 69 | }; 70 | 71 | struct schema { 72 | std::string name_; 73 | std::vector types_; 74 | std::unordered_map type_map_; 75 | }; 76 | 77 | schema parse(std::string_view); 78 | 79 | bool is_list(schema const&, std::string const& type_name); 80 | 81 | } // namespace express 82 | -------------------------------------------------------------------------------- /express/src/exp_struct_gen.cc: -------------------------------------------------------------------------------- 1 | #include "express/exp_struct_gen.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "boost/algorithm/string.hpp" 9 | 10 | #include "cista/hash.h" 11 | 12 | #include "utl/enumerate.h" 13 | #include "utl/verify.h" 14 | 15 | namespace express { 16 | 17 | static auto const special = std::map{ 18 | {"BOOLEAN", "bool"}, {"LOGICAL", "step::exp_logical"}, 19 | {"REAL", "double"}, {"INTEGER", "int"}, 20 | {"STRING", "std::string"}, {"BINARY(32)", "uint32_t"}}; 21 | 22 | std::optional is_special(schema const& s, 23 | std::string const& type_name) { 24 | if (auto const type_it = s.type_map_.find(type_name); 25 | type_it != end(s.type_map_)) { 26 | auto const& t = type_it->second; 27 | switch (t->data_type_) { 28 | case data_type::ALIAS: return is_special(s, t->alias_); 29 | case data_type::BOOL: [[fallthrough]]; 30 | case data_type::LOGICAL: return "step::exp_logical"; 31 | case data_type::REAL: [[fallthrough]]; 32 | case data_type::NUMBER: return "double"; 33 | case data_type::STRING: return "std::string"; 34 | case data_type::INTEGER: return "int"; 35 | case data_type::ENUM: return type_name; 36 | case data_type::ENTITY: return std::nullopt; 37 | case data_type::SELECT: return type_name; 38 | case data_type::BINARY: return "std::vector"; 39 | case data_type::UNKOWN: throw std::runtime_error{"unkown type"}; 40 | } 41 | } 42 | if (auto const special_it = special.find(type_name); 43 | special_it != end(special)) { 44 | return special_it->second; 45 | } 46 | return std::nullopt; 47 | } 48 | 49 | bool is_value_type(schema const& s, type const& t) { 50 | auto const it = s.type_map_.find(t.name_); 51 | utl::verify(it != end(s.type_map_), "type {} not found in type map", t.name_); 52 | if (it->second->list_) { 53 | return true; 54 | } 55 | switch (it->second->data_type_) { 56 | case data_type::ALIAS: 57 | utl::verify(t.alias_ != t.name_, "infinite alias loop for {}", t.name_); 58 | return is_value_type(s, *s.type_map_.at(t.alias_)); 59 | 60 | case data_type::BOOL: 61 | case data_type::LOGICAL: 62 | case data_type::REAL: 63 | case data_type::NUMBER: 64 | case data_type::STRING: 65 | case data_type::INTEGER: 66 | case data_type::ENUM: 67 | case data_type::BINARY: [[fallthrough]]; 68 | case data_type::SELECT: return true; 69 | 70 | case data_type::ENTITY: return false; 71 | 72 | case data_type::UNKOWN: [[fallthrough]]; 73 | default: throw std::runtime_error{"unkown type"}; 74 | } 75 | } 76 | 77 | void generate_header(std::ostream& out, schema const& s, type const& t) { 78 | out << "#pragma once\n\n"; 79 | 80 | auto const uses_optional = 81 | std::any_of(begin(t.members_), end(t.members_), 82 | [](member const& m) { return m.optional_; }); 83 | auto const uses_list = 84 | std::any_of(begin(t.members_), end(t.members_), 85 | [&](member const& m) { return m.is_list(s); }); 86 | auto const uses_string = 87 | t.data_type_ == data_type::STRING || 88 | std::any_of(begin(t.members_), end(t.members_), [&](member const& m) { 89 | auto const special = is_special(s, m.get_type_name()); 90 | return special.has_value() && *special == "std::string"; 91 | }); 92 | auto const uses_variant = t.data_type_ == data_type::SELECT; 93 | 94 | if (t.subtype_of_.empty()) { 95 | out << "#include \"step/root_entity.h\"\n"; 96 | } else { 97 | out << "#include \"" << s.name_ << "/" << t.subtype_of_ << ".h\"\n"; 98 | } 99 | 100 | if (uses_list || uses_optional || uses_string || uses_variant) { 101 | out << "\n"; 102 | } 103 | out << "#include \n" 104 | << (uses_list ? "#include \n" : "") 105 | << (uses_optional ? "#include \n" : "") 106 | << (uses_string ? "#include \n" : "") 107 | << (uses_variant ? "#include \n" : "") // 108 | << "#include \n"; 109 | if (uses_list || uses_optional || uses_string || uses_variant) { 110 | out << "\n"; 111 | } 112 | if (t.data_type_ == data_type::SELECT) { 113 | out << "#include \"step/id_t.h\"\n\n"; 114 | for (auto const& d : t.details_) { 115 | if (is_value_type(s, *s.type_map_.at(d))) { 116 | out << "#include \"" << s.name_ << "/" << d << ".h\"\n"; 117 | } 118 | } 119 | } else if (t.data_type_ == data_type::ALIAS) { 120 | out << "#include \"" << s.name_ << "/" << t.alias_ << ".h\"\n"; 121 | } else if (t.data_type_ == data_type::ENTITY) { 122 | for (auto const& m : t.members_) { 123 | if (auto const it = s.type_map_.find(m.get_type_name()); 124 | it != end(s.type_map_) && 125 | (it->second->data_type_ == data_type::ENUM || 126 | it->second->data_type_ == data_type::SELECT)) { 127 | out << "#include \"" << s.name_ << "/" << it->second->name_ << ".h\"\n"; 128 | } 129 | } 130 | } else if (t.data_type_ == data_type::LOGICAL) { 131 | out << "#include \"step/exp_logical.h\"\n"; 132 | } 133 | 134 | if (t.data_type_ == data_type::ENTITY || t.data_type_ == data_type::ENUM) { 135 | out << "\nnamespace utl { struct cstr; }\n"; 136 | } 137 | 138 | out << "\nnamespace " << s.name_ << " {\n\n"; 139 | 140 | switch (t.data_type_) { 141 | case data_type::BOOL: [[fallthrough]]; 142 | case data_type::LOGICAL: out << "using " << t.name_ << " = bool;\n"; break; 143 | 144 | case data_type::REAL: [[fallthrough]]; 145 | case data_type::NUMBER: out << "using " << t.name_ << " = double;\n"; break; 146 | 147 | case data_type::INTEGER: out << "using " << t.name_ << " = int;\n"; break; 148 | 149 | case data_type::STRING: 150 | out << "using " << t.name_ << " = std::string;\n"; 151 | break; 152 | 153 | case data_type::BINARY: 154 | out << "using " << t.name_ << " = std::vector;\n"; 155 | break; 156 | 157 | case data_type::SELECT: 158 | for (auto const& m : t.details_) { 159 | if (!is_value_type(s, *s.type_map_.at(m))) { 160 | out << "struct " << m << ";\n"; 161 | } 162 | } 163 | out << "\nstruct " << t.name_ << " {\n"; 164 | out << " friend void parse_step(utl::cstr&, " << t.name_ << "&);\n"; 165 | out << " friend void write(step::write_context const&, std::ostream&, " 166 | << t.name_ << " const&);\n"; 167 | out << " std::string_view name() const;\n"; 168 | out << " void resolve(std::vector const&);\n\n"; 169 | out << " std::variant<\n"; 170 | for (auto const& [i, v] : utl::enumerate(t.details_)) { 171 | out << " " << v << (!is_value_type(s, *s.type_map_.at(v)) ? "*" : "") 172 | << (i != t.details_.size() - 1 ? "," : "") << "\n"; 173 | } 174 | out << " > data_;\n"; 175 | out << " step::id_t tmp_id_{std::numeric_limits::max()};\n"; 176 | out << "};"; 177 | break; 178 | 179 | case data_type::ALIAS: 180 | out << "using " << t.name_ << " = "; 181 | if (t.list_) { 182 | out << "std::vector<"; 183 | } 184 | out << t.alias_ 185 | << (!is_value_type(s, *s.type_map_.at(t.alias_)) ? "*" : ""); 186 | if (t.list_) { 187 | out << ">"; 188 | } 189 | out << ";\n"; 190 | break; 191 | 192 | case data_type::ENUM: 193 | out << "enum class " << t.name_ << " {\n"; 194 | for (auto const& [i, v] : utl::enumerate(t.details_)) { 195 | out << " " << s.name_ << "_" << v 196 | << (i != t.details_.size() - 1 ? "," : "") << "\n"; 197 | } 198 | out << "};\n"; 199 | out << "void parse_step(utl::cstr&, " << t.name_ << "&);\n"; 200 | out << "void write(step::write_context const&, std::ostream&, " << t.name_ 201 | << " const&);\n"; 202 | break; 203 | 204 | case data_type::ENTITY: { 205 | for (auto const& m : t.members_) { 206 | if (auto const data_type = is_special(s, m.get_type_name()); 207 | !data_type.has_value()) { 208 | out << "struct " << m.get_type_name() << ";\n"; 209 | } 210 | } 211 | if (!t.members_.empty()) { 212 | out << "\n"; 213 | } 214 | 215 | out << "struct " << t.name_; 216 | if (t.subtype_of_.empty()) { 217 | out << " : public step::root_entity"; 218 | } else { 219 | out << " : public " << t.subtype_of_; 220 | } 221 | out << " {\n" 222 | << " static constexpr auto const NAME = \"" 223 | << boost::to_upper_copy(t.name_) << "\";\n" 224 | << " std::string_view name() const override { return NAME; }\n" 225 | << " friend void parse_step(utl::cstr&, " << t.name_ << "&);\n" 226 | << " void resolve(std::vector const&) override;\n" 227 | " void write(step::write_context const&, std::ostream&, bool " 228 | "const write_type_name) const " 229 | "override;\n"; 230 | 231 | for (auto const& m : t.members_) { 232 | auto const is_l = m.is_list(s); 233 | 234 | struct visit { 235 | visit(schema const& s, std::ostream& o) : s_{s}, out_{o} {} 236 | void operator()(express::type_name const& t) const { 237 | auto const l = is_list(s_, t.name_); 238 | if (l) { 239 | out_ << "std::vector<"; 240 | } 241 | if (auto const data_type = is_special(s_, t.name_); 242 | data_type.has_value()) { 243 | out_ << *data_type; 244 | } else { 245 | out_ << t.name_ << "*"; 246 | } 247 | if (l) { 248 | out_ << ">"; 249 | } 250 | } 251 | void operator()(express::list const& l) const { 252 | out_ << "std::vector<"; 253 | boost::apply_visitor(*this, l.m_); 254 | out_ << ">"; 255 | } 256 | schema const& s_; 257 | std::ostream& out_; 258 | }; 259 | 260 | out << " " // 261 | << (m.optional_ ? "std::optional<" : ""); 262 | 263 | boost::apply_visitor(visit{s, out}, m.type_); 264 | 265 | auto const data_type = is_special(s, m.get_type_name()); 266 | out << (m.optional_ ? ">" : "") // 267 | << " " << m.name_ << "_" 268 | << (!data_type.has_value() && !is_l && !m.optional_ ? "{nullptr}" 269 | : "") 270 | << ";\n"; 271 | } 272 | out << "};\n"; 273 | break; 274 | 275 | case data_type::UNKOWN: 276 | default: throw std::runtime_error{"unknown data type"}; 277 | } 278 | } 279 | 280 | out << "\n} // namespace " << s.name_ << "\n"; 281 | } 282 | 283 | void list_select_cases(std::ostream& out, schema const& s, 284 | std::vector> chain) { 285 | if (chain.back().first->data_type_ != data_type::SELECT) { 286 | // Recursion base case -> output case 287 | auto const [last_type, last_id] = chain.back(); 288 | chain.resize(chain.size() - 1); 289 | out << " case " 290 | << cista::hash(boost::to_upper_copy(last_type->name_)) 291 | << "U" 292 | << ": { " << s.name_ << "::" << last_type->name_ 293 | << " v; parse_step(s, v); e = "; 294 | for (auto const& [i, entry] : utl::enumerate(chain)) { 295 | auto const& [el_t, select_index] = entry; 296 | out << s.name_ << "::" << el_t->name_ << "{" 297 | << "decltype(std::declval<" << s.name_ << "::" << el_t->name_ 298 | << ">().data_){std::in_place_index_t<" << select_index << ">{}, "; 299 | } 300 | out << "v"; 301 | for (auto const& [i, c] : utl::enumerate(chain)) { 302 | out << "}}"; 303 | } 304 | out << ";"; 305 | out << " break; }\n"; 306 | } else { 307 | // Continue recursion for each type in the select. 308 | for (auto const& [i, m] : utl::enumerate(chain.back().first->details_)) { 309 | auto const m_type = *s.type_map_.at(m); 310 | if (!is_value_type(s, m_type)) { 311 | continue; // handled by ID case 312 | } 313 | auto next_chain = chain; 314 | next_chain.back().second = i; 315 | next_chain.emplace_back(&m_type, std::numeric_limits::max()); 316 | list_select_cases(out, s, std::move(next_chain)); 317 | } 318 | } 319 | } 320 | 321 | bool has_members(schema const& s, type const& t) { 322 | return !t.members_.empty() || 323 | (!t.subtype_of_.empty() && 324 | has_members(s, *s.type_map_.at(t.subtype_of_))); 325 | }; 326 | 327 | void generate_source(std::ostream& out, schema const& s, type const& t) { 328 | switch (t.data_type_) { 329 | case data_type::SELECT: { 330 | out << "#include \"" << s.name_ << "/" << t.name_ << ".h\"\n\n" 331 | << "#include \"utl/parser/cstr.h\"\n" 332 | << "#include \"utl/verify.h\"\n\n" 333 | << "#include \"step/parse_step.h\"\n" 334 | << "#include \"step/write.h\"\n" 335 | << "#include \"step/assign_entity_ptr_to_select.h\"\n" 336 | << "#include \"step/resolve.h\"\n\n" 337 | << "namespace " << s.name_ << " {\n\n"; 338 | out << "void parse_step(utl::cstr& s, " << t.name_ << "& e) {\n"; 339 | out << " using step::parse_step;\n"; 340 | out << " if (s.len != 0 && s[0] == '#') {\n"; 341 | out << " parse_step(s, e.tmp_id_);\n"; 342 | out << " return;\n"; 343 | out << " }\n"; 344 | 345 | auto const select_has_value_types = std::any_of( 346 | begin(t.details_), end(t.details_), [&](std::string const& m) { 347 | return is_value_type(s, *s.type_map_.at(m)); 348 | }); 349 | if (select_has_value_types) { 350 | out << " auto const name_end = step::get_next_token(s, '(');\n"; 351 | out << R"( utl::verify(name_end.has_value(), "expected SELECT name, got {}", s.view());)" 352 | << "\n"; 353 | out << R"( auto const name = std::string_view{s.str, static_cast(name_end->str - s.str - 1)};)" 354 | << "\n"; 355 | out << " s = *name_end;\n"; 356 | out << " switch(cista::hash(name)) {\n"; 357 | list_select_cases(out, s, 358 | {{&t, std::numeric_limits::max()}}); 359 | out << " default: utl::verify(false, \"unable to parse select " 360 | << s.name_ << "::" << t.name_ 361 | << ", got name '{}', hash={}\", name, cista::hash(name));\n"; 362 | out << " }\n"; 363 | out << R"( utl::verify(s.len != 0 && s[0] == ')', "expected select end ')', got {}", s.view());)" 364 | << "\n"; 365 | out << " ++s;\n"; 366 | } else { 367 | out << " utl::verify(false, \"select: expected id, got {}\", " 368 | "s.view());\n"; 369 | } 370 | 371 | out << "}\n\n"; 372 | out << "void " << t.name_ 373 | << "::resolve(std::vector const& m) {\n"; 374 | out << " if (tmp_id_ == step::id_t::invalid()) { return; }\n"; 375 | out << " step::assign_entity_ptr_to_select(*this, m.at(tmp_id_.id_));\n"; 376 | out << "}\n\n"; 377 | 378 | out << "std::string_view " << t.name_ << "::name() const {\n"; 379 | out << " static char const* names[] = {\n"; 380 | for (auto const& [i, m] : utl::enumerate(t.details_)) { 381 | out << " \"" << boost::to_upper_copy(m) << "\""; 382 | if (i != t.details_.size() - 1) { 383 | out << ",\n"; 384 | } else { 385 | out << "\n"; 386 | } 387 | } 388 | out << " };\n"; 389 | out << " return names[data_.index()];\n" 390 | "}\n\n"; 391 | 392 | out << "void write(step::write_context const& ctx, std::ostream& out, " 393 | << t.name_ << " const& el) {\n"; 394 | out << " std::visit([&](auto&& data) {\n" 395 | " using step::write;\n" 396 | " using Type = std::decay_t\n;" 397 | " constexpr auto const final = " 398 | "!step::has_data::value && !std::is_pointer_v;\n" 399 | " if constexpr (final) {\n" 400 | " out << el.name() << '(';\n" 401 | " }\n" 402 | " write(ctx, out, data);\n" 403 | " if constexpr (final) {\n" 404 | " out << ')';\n" 405 | " }\n" 406 | " }, el.data_);"; 407 | out << "}\n"; 408 | out << "\n} // namespace " << s.name_ << "\n\n\n"; 409 | } break; 410 | 411 | case data_type::ENTITY: 412 | out << "#include \"" << s.name_ << "/" << t.name_ << ".h\"\n\n" 413 | << "#include \"utl/parser/cstr.h\"\n\n" 414 | << "#include \"step/parse_step.h\"\n" 415 | << "#include \"step/resolve.h\"\n" 416 | << "#include \"step/write.h\"\n\n" 417 | << "namespace " << s.name_ << " {\n\n"; 418 | out << "void parse_step(utl::cstr& s, " << t.name_ << "& e) {\n"; 419 | out << " using step::parse_step;\n"; 420 | if (!t.subtype_of_.empty()) { 421 | out << " parse_step(s, *static_cast<" << t.subtype_of_ << "*>(&e));\n"; 422 | } 423 | for (auto const& m : t.members_) { 424 | out << " if (s.len > 0 && s[0] == '*') {\n" 425 | << " ++s;\n" 426 | << " } else {\n" 427 | << " parse_step(s, e." << m.name_ << "_);\n" 428 | << " }\n" 429 | << " if (s.len > 0 && s[0] == ',') {\n" 430 | << " ++s;\n" 431 | << " }\n" 432 | << " s = s.skip_whitespace_front();\n\n"; 433 | } 434 | out << "}\n\n"; 435 | out << "void " << t.name_ 436 | << "::resolve(std::vector const& m) {\n"; 437 | if (!t.subtype_of_.empty()) { 438 | out << " " << t.subtype_of_ << "::resolve(m);\n"; 439 | } 440 | for (auto const& m : t.members_) { 441 | if (auto const member_type_it = s.type_map_.find(m.get_type_name()); 442 | member_type_it != end(s.type_map_) && 443 | (member_type_it->second->data_type_ == data_type::ENTITY || 444 | member_type_it->second->data_type_ == data_type::SELECT)) { 445 | out << " step::resolve(m, " << m.name_ << "_);\n"; 446 | } 447 | } 448 | out << "}\n"; 449 | 450 | out << "void " << t.name_ 451 | << "::write(step::write_context const& ctx, std::ostream& out, bool " 452 | "const write_type_name) const " 453 | "{\n" 454 | " using step::write;\n" 455 | " if (write_type_name) { out << \"" 456 | << boost::to_upper_copy(t.name_) << "(\"; }\n"; 457 | if (!t.subtype_of_.empty()) { 458 | out << " " << t.subtype_of_ << "::write(ctx, out, false);\n"; 459 | if (!t.members_.empty() && 460 | has_members(s, *s.type_map_.at(t.subtype_of_))) { 461 | out << " out << \", \";\n"; 462 | } 463 | } 464 | 465 | if (!t.members_.empty()) { 466 | for (auto const& [i, m] : utl::enumerate(t.members_)) { 467 | out << " write(ctx, out, " << m.name_ << "_);\n"; 468 | if (i != t.members_.size() - 1) { 469 | out << " out << \", \";\n"; 470 | } 471 | } 472 | } 473 | out << " if (write_type_name) { out << \");\"; }\n"; 474 | out << "}\n"; 475 | 476 | out << "\n} // namespace " << s.name_ << "\n\n\n"; 477 | 478 | break; 479 | 480 | case data_type::ENUM: 481 | out << "#include \"" << s.name_ << "/" << t.name_ << ".h\"\n\n"; 482 | out << "#include \"utl/parser/cstr.h\"\n"; 483 | out << "#include \"utl/verify.h\"\n\n"; 484 | out << "#include \"cista/hash.h\"\n\n"; 485 | out << "#include \"step/parse_step.h\"\n\n"; 486 | out << "namespace " << s.name_ << " {\n\n"; 487 | out << "void parse_step(utl::cstr& s, " << t.name_ << "& v) {\n"; 488 | out << " utl::verify(s.len != 0 && s[0] == '.', \"expected enum start " 489 | "'.', got {}\", s.view());\n"; 490 | out << " ++s;\n"; 491 | out << " auto const end = step::get_next_token(s, '.');\n"; 492 | out << " utl::verify(end.has_value(), \"enum end not found, got {}\", " 493 | "s.view());\n"; 494 | out << " auto const str = std::string_view{s.str, " 495 | "static_cast(end->str - s.str - 1)};\n"; 496 | out << " switch(cista::hash(str)) {\n"; 497 | for (auto const& [i, m] : utl::enumerate(t.details_)) { 498 | out << " case " << cista::hash(m) << "U: v = " << t.name_ 499 | << "::" << s.name_ << "_" << m << "; break;\n"; 500 | } 501 | out << " default: utl::verify(false, \"expected enum value, got {}\", " 502 | "str);\n"; 503 | out << " }\n"; 504 | out << " s = *end;\n"; 505 | out << "}\n\n"; 506 | out << "void write(step::write_context const&, std::ostream& out, " 507 | << t.name_ << " const& val) {\n" 508 | << " switch (val) {\n"; 509 | for (auto const& [i, m] : utl::enumerate(t.details_)) { 510 | out << " case " << t.name_ << "::" << s.name_ << "_" << m 511 | << ": out << \"." << m << ".\"; break;\n"; 512 | } 513 | out << " default: throw std::runtime_error{\"unknown enum value\"};\n"; 514 | out << " }\n"; 515 | out << "}\n\n"; 516 | out << "} // namespace " << s.name_ << "\n\n\n"; 517 | break; 518 | 519 | default: /* skip */ break; 520 | } 521 | } 522 | 523 | } // namespace express 524 | -------------------------------------------------------------------------------- /express/src/parse_exp.cc: -------------------------------------------------------------------------------- 1 | #include "express/parse_exp.h" 2 | 3 | #include "boost/foreach.hpp" 4 | #include "boost/fusion/include/adapt_struct.hpp" 5 | #include "boost/spirit/include/phoenix_core.hpp" 6 | #include "boost/spirit/include/phoenix_fusion.hpp" 7 | #include "boost/spirit/include/phoenix_object.hpp" 8 | #include "boost/spirit/include/phoenix_operator.hpp" 9 | #include "boost/spirit/include/phoenix_stl.hpp" 10 | #include "boost/spirit/include/qi.hpp" 11 | 12 | #include "utl/verify.h" 13 | 14 | namespace phoenix = boost::phoenix; 15 | namespace qi = boost::spirit::qi; 16 | namespace ascii = boost::spirit::ascii; 17 | 18 | // clang-format off 19 | BOOST_FUSION_ADAPT_STRUCT( 20 | express::schema, 21 | (std::string, name_) 22 | (std::vector, types_) 23 | ); 24 | BOOST_FUSION_ADAPT_STRUCT( 25 | express::list, 26 | (unsigned, min_) 27 | (unsigned, max_) 28 | (express::member_type, m_) 29 | ); 30 | BOOST_FUSION_ADAPT_STRUCT( 31 | express::type_name, 32 | (std::string, name_) 33 | ); 34 | BOOST_FUSION_ADAPT_STRUCT( 35 | express::member, 36 | (std::string, name_) 37 | (express::member_type, type_) 38 | (bool, optional_) 39 | ); 40 | BOOST_FUSION_ADAPT_STRUCT( 41 | express::type, 42 | (std::string, name_) 43 | (express::data_type, data_type_) 44 | (std::vector, details_) 45 | (std::string, subtype_of_) 46 | (std::vector, members_) 47 | (bool, list_) 48 | (unsigned, min_size_) 49 | (unsigned, max_size_) 50 | (std::string, alias_) 51 | ); 52 | // clang-format on 53 | 54 | namespace express { 55 | 56 | bool is_list(schema const& s, std::string const& type_name) { 57 | if (auto const type_it = s.type_map_.find(type_name); 58 | type_it != end(s.type_map_)) { 59 | if (type_it->second->data_type_ == data_type::ALIAS) { 60 | return is_list(s, type_it->second->alias_); 61 | } else { 62 | return type_it->second->list_; 63 | } 64 | } else { 65 | return false; 66 | } 67 | } 68 | 69 | bool member::is_list(schema const& s) const { 70 | struct visit { 71 | explicit visit(schema const& s) : s_{s} {} 72 | bool operator()(type_name const& s) const { 73 | return express::is_list(s_, s.name_); 74 | } 75 | bool operator()(express::list const& l) const { return true; } 76 | schema const& s_; 77 | }; 78 | return boost::apply_visitor(visit{s}, type_); 79 | } 80 | 81 | std::string const& member::get_type_name() const { 82 | struct visit { 83 | std::string const& operator()(type_name const& s) const { return s.name_; } 84 | std::string const& operator()(express::list const& l) const { 85 | return boost::apply_visitor(*this, l.m_); 86 | } 87 | }; 88 | return boost::apply_visitor(visit{}, type_); 89 | } 90 | 91 | template 92 | struct express_grammar : qi::grammar { 93 | express_grammar() : express_grammar::base_type{express_} { 94 | using phoenix::at_c; 95 | using phoenix::push_back; 96 | using qi::as_string; 97 | using qi::bool_; 98 | using qi::char_; 99 | using qi::int_; 100 | using qi::lexeme; 101 | using qi::matches; 102 | using qi::space; 103 | using qi::string; 104 | using qi::symbols; 105 | using namespace qi::labels; 106 | 107 | // clang-format off 108 | data_type_.add 109 | ("BOOL", data_type::BOOL) 110 | ("LOGICAL", data_type::LOGICAL) 111 | ("REAL", data_type::REAL) 112 | ("NUMBER", data_type::NUMBER) 113 | ("STRING", data_type::STRING) 114 | ("INTEGER", data_type::INTEGER) 115 | ("ENTITY", data_type::ENTITY) 116 | ("ENUM", data_type::ENUM) 117 | ("BINARY", data_type::BINARY) 118 | ("SELECT", data_type::SELECT); 119 | // clang-format on 120 | 121 | enum_ = "TYPE " // 122 | >> 123 | as_string[lexeme[+(char_ - '=' - space)]] 124 | [at_c<0>(_val) = _1, at_c<1>(_val) = data_type::ENUM] // 125 | >> '=' >> "ENUMERATION" >> "OF" >> '(' >> 126 | (as_string[*(char_ - char_(",)") - space)] % 127 | (*space >> ','))[at_c<2>(_val) = _1] >> 128 | *(char_ - "END_TYPE;") // 129 | >> "END_TYPE;"; 130 | 131 | select_ = 132 | "TYPE " // 133 | >> as_string[lexeme[+(char_ - '=' - space)]] 134 | [at_c<0>(_val) = _1, at_c<1>(_val) = data_type::SELECT] // 135 | >> "= SELECT" // 136 | >> '(' >> (as_string[lexeme[*(char_ - char_(",)") - space)]] % 137 | ',')[at_c<2>(_val) = _1] // 138 | >> *(char_ - "END_TYPE;") // 139 | >> "END_TYPE;"; 140 | 141 | type_ = "TYPE " // 142 | >> as_string[lexeme[+(char_ - '=' - space)]][at_c<0>(_val) = _1] >> 143 | '=' // 144 | >> qi::matches[(string("LIST") | "ARRAY" | "SET") >> '[' >> 145 | (int_[at_c<6>(_val) = _1] | '?') >> ':' >> 146 | (int_[at_c<7>(_val) = _1] | '?') >> ']' >> "OF"] 147 | [at_c<5>(_val) = _1] >> 148 | (data_type_[at_c<1>(_val) = _1] | 149 | as_string[lexeme[+(char_ - ';' - space)]] 150 | [at_c<8>(_val) = _1, 151 | at_c<1>(_val) = data_type::ALIAS]) // data type 152 | >> *(char_ - "END_TYPE;") // 153 | >> "END_TYPE;"; 154 | 155 | list_ = -string("UNIQUE") >> (string("LIST") | "ARRAY" | "SET") >> '[' >> 156 | (int_[at_c<0>(_val) = _1] | '?') >> ':' >> 157 | (int_[at_c<1>(_val) = _1] | '?') >> ']' >> "OF" >> 158 | member_type_[at_c<2>(_val) = _1]; 159 | 160 | type_name_ = -string("UNIQUE") >> 161 | as_string[lexeme[+(char_ - space - ';')]][at_c<0>(_val) = _1]; 162 | 163 | member_type_ = list_ | type_name_; 164 | 165 | member_ = as_string[lexeme[+(char_ - space)]][at_c<0>(_val) = _1] // 166 | >> ':' >> qi::matches["OPTIONAL"][at_c<2>(_val) = _1] // 167 | >> member_type_[at_c<1>(_val) = _1] // 168 | >> ';'; 169 | 170 | entity_ = 171 | "ENTITY" // 172 | >> as_string[lexeme[*(char_ - space - ';')]] 173 | [at_c<0>(_val) = _1, 174 | at_c<1>(_val) = data_type::ENTITY] // type name 175 | >> -(-string("ABSTRACT") >> "SUPERTYPE" >> "OF" >> '(' >> 176 | ((string("ONEOF") >> '(' >> *(char_ - ')') >> ')') | 177 | *(char_ - ')')) >> 178 | ')') // 179 | >> -("SUBTYPE OF (" >> 180 | as_string[lexeme[+(char_ - ')')]][at_c<3>(_val) = _1] >> ")") // 181 | >> ';' // 182 | >> *(member_[push_back(at_c<4>(_val), _1)]) // 183 | >> -((string("INVERSE") | "WHERE" | "UNIQUE" | "DERIVE") >> 184 | *(char_ - "END_ENTITY;")) // 185 | >> "END_ENTITY;"; 186 | 187 | schema_ = "SCHEMA " // 188 | >> as_string[lexeme[+(char_ - ';')]][at_c<0>(_val) = _1] >> 189 | ';' // 190 | >> *(enum_[push_back(at_c<1>(_val), _1)] | 191 | entity_[push_back(at_c<1>(_val), _1)] | 192 | select_[push_back(at_c<1>(_val), _1)] | 193 | type_[push_back(at_c<1>(_val), _1)]) // 194 | >> *(char_ - "END_SCHEMA") >> "END_SCHEMA"; 195 | 196 | comment_ = "(*" >> *(char_ - "*)") >> "*)"; 197 | 198 | express_ = *comment_ >> schema_[_val = _1]; 199 | } 200 | 201 | qi::symbols data_type_; 202 | qi::rule type_name_; 203 | qi::rule list_; 204 | qi::rule member_type_; 205 | qi::rule member_; 206 | qi::rule enum_; 207 | qi::rule select_; 208 | qi::rule type_; 209 | qi::rule entity_; 210 | qi::rule schema_; 211 | qi::rule comment_; 212 | qi::rule express_; 213 | }; 214 | 215 | schema parse(std::string_view s) { 216 | using boost::spirit::ascii::space; 217 | 218 | schema schema; 219 | auto iter = &s[0]; // NOLINT 220 | auto end = &s[s.size() - 1] + 1; // NOLINT 221 | auto comment = express_grammar{}; 222 | auto const is_success = phrase_parse(iter, end, comment, space, schema); 223 | utl::verify_ex(is_success, parser_exception{iter, end}); 224 | for (auto const& t : schema.types_) { 225 | schema.type_map_[t.name_] = &t; 226 | } 227 | return schema; 228 | } 229 | 230 | } // namespace express 231 | -------------------------------------------------------------------------------- /express/test/exp_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "doctest/doctest.h" 9 | 10 | #include "boost/filesystem.hpp" 11 | 12 | #include "cista/mmap.h" 13 | 14 | #include "express/exp_struct_gen.h" 15 | #include "express/parse_exp.h" 16 | 17 | #include "test_dir.h" 18 | 19 | using namespace express; 20 | 21 | std::set get_subtypes_of(schema const& s, 22 | std::string_view supertype) { 23 | std::unordered_map> subtypes; 24 | for (auto const& t : s.types_) { 25 | subtypes[t.subtype_of_].emplace_back(t.name_); 26 | } 27 | 28 | std::queue q; 29 | q.emplace(supertype); 30 | 31 | std::set rec_subtypes; 32 | while (!q.empty()) { 33 | auto const next = q.front(); 34 | q.pop(); 35 | if (!rec_subtypes.emplace(next).second) { 36 | continue; 37 | } 38 | if (auto const it = subtypes.find(next); it != end(subtypes)) { 39 | for (auto const& n : it->second) { 40 | q.emplace(n); 41 | } 42 | } 43 | } 44 | return rec_subtypes; 45 | } 46 | 47 | constexpr auto const* const str = 48 | // "(*\n" 49 | // " Comment\n" 50 | // "*)\n\n" 51 | "SCHEMA IFC2X3;\n" 52 | "\n" 53 | "TYPE IfcAbsorbedDoseMeasure = REAL;\n" 54 | "END_TYPE;\n" 55 | "\n" 56 | "TYPE IfcAccelerationMeasure = REAL;\n" 57 | "END_TYPE;\n" 58 | "\n" 59 | "TYPE IfcNormalisedRatioMeasure = IfcRatioMeasure;\n" 60 | " WHERE\n" 61 | " WR1 : {0.0 <= SELF <= 1.0};\n" 62 | "END_TYPE;\n" 63 | "\n" 64 | "TYPE IfcGeometricSetSelect = SELECT\n" 65 | " (IfcPoint\n" 66 | " ,IfcCurve\n" 67 | " ,IfcSurface\n);" 68 | "END_TYPE;\n" 69 | "\n" 70 | "ENTITY IfcPort\n" 71 | " ABSTRACT SUPERTYPE OF (ONEOF\n" 72 | " (IfcDistributionPort))\n" 73 | " SUBTYPE OF (IfcProduct);\n" 74 | " INVERSE\n" 75 | " ContainedIn : IfcRelConnectsPortToElement FOR RelatingPort;\n" 76 | " ConnectedFrom : SET [0:1] OF IfcRelConnectsPorts FOR RelatedPort;\n" 77 | " ConnectedTo : SET [0:1] OF IfcRelConnectsPorts FOR RelatingPort;\n" 78 | "END_ENTITY;\n" 79 | "\n" 80 | "TYPE IfcActionTypeEnum = ENUMERATION OF\n" 81 | " (PERMANENT_G\n" 82 | " ,VARIABLE_Q\n" 83 | " ,EXTRAORDINARY_A\n" 84 | " ,USERDEFINED\n" 85 | " ,NOTDEFINED);\n" 86 | "END_TYPE;\n" 87 | "\n" 88 | "ENTITY IfcTimeSeries\n" 89 | " ABSTRACT SUPERTYPE OF (ONEOF\n" 90 | " (IfcIrregularTimeSeries\n" 91 | " ,IfcRegularTimeSeries));\n" 92 | " Name : IfcLabel;\n" 93 | " Description : OPTIONAL IfcText;\n" 94 | " StartTime : IfcDateTimeSelect;\n" 95 | " EndTime : IfcDateTimeSelect;\n" 96 | " TimeSeriesDataType : IfcTimeSeriesDataTypeEnum;\n" 97 | " DataOrigin : IfcDataOriginEnum;\n" 98 | " UserDefinedDataOrigin : OPTIONAL IfcLabel;\n" 99 | " Unit : OPTIONAL IfcUnit;\n" 100 | " INVERSE\n" 101 | " DocumentedBy : SET [0:1] OF IfcTimeSeriesReferenceRelationship FOR " 102 | "ReferencedTimeSeries;\n" 103 | "END_ENTITY;\n" 104 | "\n" 105 | "ENTITY Ifc2DCompositeCurve\n" 106 | " SUBTYPE OF (IfcCompositeCurve);\n" 107 | " WHERE\n" 108 | " WR1 : SELF\\IfcCompositeCurve.ClosedCurve;\n" 109 | " WR2 : SELF\\IfcCurve.Dim = 2;\n" 110 | "END_ENTITY;\n" 111 | "\n" 112 | "ENTITY IfcElement\n" 113 | " ABSTRACT SUPERTYPE OF (ONEOF\n" 114 | " (IfcBuildingElement\n" 115 | " ,IfcDistributionElement\n" 116 | " ,IfcElectricalElement\n" 117 | " ,IfcElementAssembly\n" 118 | " ,IfcElementComponent\n" 119 | " ,IfcEquipmentElement\n" 120 | " ,IfcFeatureElement\n" 121 | " ,IfcFurnishingElement\n" 122 | " ,IfcTransportElement\n" 123 | " ,IfcVirtualElement))\n" 124 | " SUBTYPE OF (IfcProduct);\n" 125 | " Tag : OPTIONAL IfcIdentifier;\n" 126 | " INVERSE\n" 127 | " HasStructuralMember : SET [0:?] OF IfcRelConnectsStructuralElement FOR " 128 | "RelatingElement;\n" 129 | "END_ENTITY;" 130 | "\n" 131 | "END_SCHEMA"; 132 | 133 | TEST_CASE("abstract supertype") { 134 | constexpr auto const* const s = 135 | "SCHEMA IFC2X3;\n" 136 | "\n" 137 | "ENTITY IfcTimeSeries\n" 138 | " ABSTRACT SUPERTYPE OF (ONEOF\n" 139 | " (IfcIrregularTimeSeries\n" 140 | " ,IfcRegularTimeSeries));\n" 141 | " Name : IfcLabel;\n" 142 | " Description : OPTIONAL IfcText;\n" 143 | " StartTime : IfcDateTimeSelect;\n" 144 | " EndTime : IfcDateTimeSelect;\n" 145 | " TimeSeriesDataType : IfcTimeSeriesDataTypeEnum;\n" 146 | " DataOrigin : IfcDataOriginEnum;\n" 147 | " UserDefinedDataOrigin : OPTIONAL IfcLabel;\n" 148 | " Unit : OPTIONAL IfcUnit;\n" 149 | " INVERSE\n" 150 | " DocumentedBy : SET [0:1] OF IfcTimeSeriesReferenceRelationship FOR " 151 | "ReferencedTimeSeries;\n" 152 | "END_ENTITY;\n" 153 | "\n" 154 | "END_SCHEMA"; 155 | 156 | auto const schema = parse(s); 157 | REQUIRE(schema.types_.size() == 1); 158 | CHECK(schema.types_.front().name_ == "IfcTimeSeries"); 159 | 160 | auto const ifc_time_series = schema.types_.front(); 161 | auto i = 0U; 162 | CHECK(ifc_time_series.members_[i++].name_ == "Name"); 163 | CHECK(ifc_time_series.members_[i++].name_ == "Description"); 164 | CHECK(ifc_time_series.members_[i++].name_ == "StartTime"); 165 | CHECK(ifc_time_series.members_[i++].name_ == "EndTime"); 166 | CHECK(ifc_time_series.members_[i++].name_ == "TimeSeriesDataType"); 167 | CHECK(ifc_time_series.members_[i++].name_ == "DataOrigin"); 168 | CHECK(ifc_time_series.members_[i++].name_ == "UserDefinedDataOrigin"); 169 | CHECK(ifc_time_series.members_[i++].name_ == "Unit"); 170 | 171 | i = 0U; 172 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcLabel"); 173 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcText"); 174 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcDateTimeSelect"); 175 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcDateTimeSelect"); 176 | CHECK(ifc_time_series.members_[i++].get_type_name() == 177 | "IfcTimeSeriesDataTypeEnum"); 178 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcDataOriginEnum"); 179 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcLabel"); 180 | CHECK(ifc_time_series.members_[i++].get_type_name() == "IfcUnit"); 181 | 182 | i = 0U; 183 | CHECK(ifc_time_series.members_[i++].optional_ == false); 184 | CHECK(ifc_time_series.members_[i++].optional_ == true); 185 | CHECK(ifc_time_series.members_[i++].optional_ == false); 186 | CHECK(ifc_time_series.members_[i++].optional_ == false); 187 | CHECK(ifc_time_series.members_[i++].optional_ == false); 188 | CHECK(ifc_time_series.members_[i++].optional_ == false); 189 | CHECK(ifc_time_series.members_[i++].optional_ == true); 190 | CHECK(ifc_time_series.members_[i++].optional_ == true); 191 | } 192 | 193 | TEST_CASE("express parser: ifc address") { 194 | constexpr auto const* const input = R"( 195 | SCHEMA IFC2X3; 196 | 197 | ENTITY IfcAddress 198 | ABSTRACT SUPERTYPE OF (ONEOF 199 | (IfcPostalAddress 200 | ,IfcTelecomAddress)); 201 | Purpose : OPTIONAL IfcAddressTypeEnum; 202 | Description : OPTIONAL IfcText; 203 | UserDefinedPurpose : OPTIONAL IfcLabel; 204 | INVERSE 205 | OfPerson : SET [0:?] OF IfcPerson FOR Addresses; 206 | OfOrganization : SET [0:?] OF IfcOrganization FOR Addresses; 207 | WHERE 208 | WR1 : (NOT(EXISTS(Purpose))) OR 209 | ((Purpose <> IfcAddressTypeEnum.USERDEFINED) OR 210 | ((Purpose = IfcAddressTypeEnum.USERDEFINED) AND 211 | EXISTS(SELF.UserDefinedPurpose))); 212 | END_ENTITY; 213 | 214 | END_SCHEMA 215 | )"; 216 | 217 | auto const schema = parse(input); 218 | REQUIRE(schema.types_.size() == 1); 219 | } 220 | 221 | TEST_CASE("express parser: LIST OF") { 222 | constexpr auto const* const input = R"( 223 | SCHEMA IFC2X3; 224 | 225 | ENTITY IfcTypeProduct 226 | SUPERTYPE OF (ONEOF 227 | (IfcDoorStyle 228 | ,IfcElementType 229 | ,IfcWindowStyle)) 230 | SUBTYPE OF (IfcTypeObject); 231 | RepresentationMaps : OPTIONAL LIST [1:?] OF UNIQUE IfcRepresentationMap; 232 | Tag : OPTIONAL IfcLabel; 233 | WHERE 234 | WR41 : NOT(EXISTS(SELF\IfcTypeObject.ObjectTypeOf[1])) OR 235 | (SIZEOF(QUERY(temp <* SELF\IfcTypeObject.ObjectTypeOf[1].RelatedObjects | 236 | NOT('IFC2X3.IFCPRODUCT' IN TYPEOF(temp))) 237 | ) = 0); 238 | END_ENTITY; 239 | 240 | END_SCHEMA 241 | )"; 242 | 243 | auto const schema = parse(input); 244 | REQUIRE(schema.types_.size() == 1); 245 | REQUIRE(schema.types_.front().members_.size() == 2); 246 | 247 | auto const& m = schema.types_.front().members_.front(); 248 | CHECK(m.name_ == "RepresentationMaps"); 249 | CHECK(m.is_list(schema)); 250 | CHECK(m.get_type_name() == "IfcRepresentationMap"); 251 | } 252 | 253 | TEST_CASE("parse test schema") { 254 | auto const schema = parse(str); 255 | REQUIRE(schema.types_.size() == 9U); 256 | 257 | auto const& ifc_port = schema.types_[4]; 258 | CHECK(ifc_port.name_ == "IfcPort"); 259 | CHECK(ifc_port.subtype_of_ == "IfcProduct"); 260 | } 261 | 262 | TEST_CASE("parse ifc site") { 263 | constexpr auto const* const exp_input = R"( 264 | SCHEMA IFC2X3; 265 | 266 | TYPE IfcCompoundPlaneAngleMeasure = LIST [3:4] OF INTEGER; 267 | WHERE 268 | WR1 : { -360 <= SELF[1] < 360 }; 269 | WR2 : { -60 <= SELF[2] < 60 }; 270 | WR3 : { -60 <= SELF[3] < 60 }; 271 | WR4 : ((SELF[1] >= 0) AND (SELF[2] >= 0) AND (SELF[3] >= 0)) OR ((SELF[1] <= 0) AND (SELF[2] <= 0) AND (SELF[3] <= 0)); 272 | END_TYPE; 273 | 274 | ENTITY IfcSite 275 | SUBTYPE OF (IfcSpatialStructureElement); 276 | RefLatitude : OPTIONAL IfcCompoundPlaneAngleMeasure; 277 | RefLongitude : OPTIONAL IfcCompoundPlaneAngleMeasure; 278 | RefElevation : OPTIONAL IfcLengthMeasure; 279 | LandTitleNumber : OPTIONAL IfcLabel; 280 | SiteAddress : OPTIONAL IfcPostalAddress; 281 | END_ENTITY; 282 | 283 | END_SCHEMA 284 | )"; 285 | 286 | auto const schema = parse(exp_input); 287 | REQUIRE(schema.types_.size() == 2U); 288 | 289 | auto const& angle = schema.types_.at(0); 290 | CHECK(angle.list_ == true); 291 | CHECK(angle.data_type_ == data_type::INTEGER); 292 | 293 | auto const& site = schema.types_.at(1); 294 | CHECK(site.name_ == "IfcSite"); 295 | CHECK(boost::get(site.members_.at(1).type_).name_ == 296 | "IfcCompoundPlaneAngleMeasure"); 297 | } 298 | 299 | TEST_CASE("parse ifc schema") { 300 | boost::filesystem::current_path(TEST_EXECUTION_DIR); 301 | auto const f = 302 | cista::mmap{"express/test/ifc23.txt", cista::mmap::protection::READ}; 303 | auto const schema = parse( 304 | std::string_view{reinterpret_cast(f.data()), f.size()}); 305 | 306 | for (auto const& t : schema.types_) { 307 | std::cout << "- " << t.name_ << " " 308 | << data_type_str[static_cast>( 309 | t.data_type_)]; 310 | if (!t.alias_.empty()) { 311 | std::cout << " ALIAS=\"" << t.alias_ << "\""; 312 | } 313 | if (t.list_) { 314 | std::cout << " LIST[" << t.min_size_ << ", " << t.max_size_ << "]"; 315 | } 316 | std::cout << "\n"; 317 | for (auto const& m : t.members_) { 318 | std::cout << "- name=\"" << m.name_ << "\", type=\""; 319 | struct visit { 320 | void operator()(express::type_name const& s) { 321 | std::cout << s.name_ << "\n"; 322 | } 323 | void operator()(express::list const& l) { 324 | std::cout << "LIST[" << l.min_ << ", " << l.max_ << "] "; 325 | boost::apply_visitor([&](auto&& el) { (*this)(el); }, l.m_); 326 | } 327 | }; 328 | boost::apply_visitor(visit{}, m.type_); 329 | std::cout << "\" "; 330 | std::cout << (m.optional_ ? "OPTIONAL" : "") << "\n"; 331 | } 332 | } 333 | 334 | CHECK(get_subtypes_of(schema, "IfcProduct").size() == 90); 335 | } 336 | 337 | TEST_CASE("alias to SET") { 338 | constexpr auto const* exp_input = R"( 339 | SCHEMA IFC2X3; 340 | 341 | ENTITY IfcRoot 342 | ABSTRACT SUPERTYPE OF (ONEOF 343 | (IfcObjectDefinition 344 | ,IfcPropertyDefinition 345 | ,IfcRelationship)); 346 | GlobalId : IfcGloballyUniqueId; 347 | OwnerHistory : OPTIONAL IfcOwnerHistory; 348 | Name : OPTIONAL IfcLabel; 349 | Description : OPTIONAL IfcText; 350 | UNIQUE 351 | UR1 : GlobalId; 352 | END_ENTITY; 353 | 354 | TYPE IfcPropertySetDefinitionSet = SET [1:?] OF IfcPropertySetDefinition; 355 | END_TYPE; 356 | 357 | ENTITY IfcPropertyDefinition 358 | ABSTRACT SUPERTYPE OF (ONEOF 359 | (IfcPropertySetDefinition 360 | ,IfcPropertyTemplateDefinition)) 361 | SUBTYPE OF (IfcRoot); 362 | INVERSE 363 | HasContext : SET [0:1] OF IfcRelDeclares FOR RelatedDefinitions; 364 | HasAssociations : SET [0:?] OF IfcRelAssociates FOR RelatedObjects; 365 | END_ENTITY; 366 | 367 | ENTITY IfcPropertySetDefinition 368 | ABSTRACT SUPERTYPE OF (ONEOF 369 | (IfcPreDefinedPropertySet 370 | ,IfcPropertySet 371 | ,IfcQuantitySet)) 372 | SUBTYPE OF (IfcPropertyDefinition); 373 | INVERSE 374 | DefinesType : SET [0:?] OF IfcTypeObject FOR HasPropertySets; 375 | IsDefinedBy : SET [0:?] OF IfcRelDefinesByTemplate FOR RelatedPropertySets; 376 | DefinesOccurrence : SET [0:?] OF IfcRelDefinesByProperties FOR RelatingPropertyDefinition; 377 | END_ENTITY; 378 | 379 | TYPE IfcPropertySetDefinitionSelect = SELECT 380 | (IfcPropertySetDefinition 381 | ,IfcPropertySetDefinitionSet); 382 | END_TYPE; 383 | 384 | ENTITY IfcRelDefinesByProperties 385 | SUBTYPE OF (IfcRelDefines); 386 | RelatingPropertyDefinition : IfcPropertySetDefinitionSelect; 387 | WHERE 388 | NoRelatedTypeObject : SIZEOF(QUERY(Types <* SELF\IfcRelDefinesByProperties.RelatedObjects | 'IFC4.IFCTYPEOBJECT' IN TYPEOF(Types))) = 0; 389 | END_ENTITY; 390 | 391 | END_SCHEMA)"; 392 | 393 | auto const schema = parse(exp_input); 394 | REQUIRE(schema.types_.size() == 6U); 395 | 396 | auto const& x = *schema.type_map_.at("IfcPropertySetDefinitionSet"); 397 | CHECK(x.list_); 398 | CHECK(x.alias_ == "IfcPropertySetDefinition"); 399 | CHECK(x.data_type_ == data_type::ALIAS); 400 | 401 | std::stringstream ss; 402 | for (auto const& t : schema.types_) { 403 | CHECK_NOTHROW(generate_header(ss, schema, t)); 404 | } 405 | } 406 | 407 | TEST_CASE("list of list") { 408 | constexpr auto const* exp_input = R"( 409 | SCHEMA IFC2X3; 410 | 411 | TYPE IfcLengthMeasure = REAL; 412 | END_TYPE; 413 | 414 | ENTITY IfcCartesianPoint 415 | SUBTYPE OF (IfcPoint); 416 | Coordinates : LIST [1:3] OF IfcLengthMeasure; 417 | DERIVE 418 | Dim : IfcDimensionCount := HIINDEX(Coordinates); 419 | WHERE 420 | CP2Dor3D : HIINDEX(Coordinates) >= 2; 421 | END_ENTITY; 422 | 423 | ENTITY IfcBSplineSurface 424 | ABSTRACT SUPERTYPE OF (ONEOF 425 | (IfcBSplineSurfaceWithKnots)) 426 | SUBTYPE OF (IfcBoundedSurface); 427 | ControlPointsList : LIST [2:?] OF LIST [2:?] OF IfcCartesianPoint; 428 | DERIVE 429 | UUpper : IfcInteger := SIZEOF(ControlPointsList) - 1; 430 | VUpper : IfcInteger := SIZEOF(ControlPointsList[1]) - 1; 431 | ControlPoints : ARRAY [0:UUpper] OF ARRAY [0:VUpper] OF IfcCartesianPoint := IfcMakeArrayOfArray(ControlPointsList, 432 | 0,UUpper,0,VUpper); 433 | END_ENTITY; 434 | 435 | END_SCHEMA)"; 436 | 437 | auto const schema = parse(exp_input); 438 | 439 | std::stringstream ss; 440 | for (auto const& t : schema.types_) { 441 | CHECK_NOTHROW(generate_header(ss, schema, t)); 442 | } 443 | } 444 | 445 | TEST_CASE("alias vector member") { 446 | constexpr auto const* exp_input = R"( 447 | SCHEMA IFC2X3; 448 | 449 | ENTITY IfcSpatialStructureElementType 450 | ABSTRACT SUPERTYPE OF (ONEOF 451 | (IfcSpaceType)) 452 | SUBTYPE OF (IfcElementType); 453 | END_ENTITY; 454 | 455 | TYPE IfcCompoundPlaneAngleMeasure = LIST [3:4] OF INTEGER; 456 | WHERE 457 | WR1 : { -360 <= SELF[1] < 360 }; 458 | WR2 : { -60 <= SELF[2] < 60 }; 459 | WR3 : { -60 <= SELF[3] < 60 }; 460 | WR4 : ((SELF[1] >= 0) AND (SELF[2] >= 0) AND (SELF[3] >= 0)) OR ((SELF[1] <= 0) AND (SELF[2] <= 0) AND (SELF[3] <= 0)); 461 | END_TYPE; 462 | 463 | ENTITY IfcSite; 464 | RefLatitude : OPTIONAL IfcCompoundPlaneAngleMeasure; 465 | RefLongitude : OPTIONAL IfcCompoundPlaneAngleMeasure; 466 | END_ENTITY; 467 | 468 | END_SCHEMA 469 | )"; 470 | 471 | auto const schema = parse(exp_input); 472 | 473 | std::stringstream ss; 474 | for (auto const& t : schema.types_) { 475 | CHECK_NOTHROW(generate_header(ss, schema, t)); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /express/test/main.cc: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest/doctest.h" -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixguendling/express2cpp/c4cb7117070e5fc733c8012078171409fdfe8d75/logo.png -------------------------------------------------------------------------------- /step/include/step/assign_entity_ptr_to_select.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "step/has_data.h" 6 | #include "step/root_entity.h" 7 | 8 | namespace step { 9 | 10 | namespace { 11 | 12 | template 13 | bool iterate_variant_impl(Fn&& f, std::variant v, 14 | std::index_sequence) { 15 | return (f(Is, std::variant_alternative_t()) || ...); 16 | } 17 | 18 | template 19 | bool iterate_variant(Fn&& f, std::variant v) { 20 | return iterate_variant_impl(std::forward(f), std::forward(v), 21 | std::index_sequence_for()); 22 | } 23 | 24 | } // namespace 25 | 26 | template 27 | bool assign_entity_ptr_to_select(T& t, root_entity* entity) { 28 | return iterate_variant( 29 | [&](std::size_t const index, auto&& el) { 30 | using Type = std::decay_t; 31 | if constexpr (std::is_pointer_v) { 32 | el = dynamic_cast(entity); 33 | if (el != nullptr) { 34 | t.data_ = el; 35 | return true; 36 | } 37 | } else if constexpr (has_data::value) { 38 | if (assign_entity_ptr_to_select(el, entity)) { 39 | t.data_ = el; 40 | return true; 41 | } 42 | } 43 | return false; 44 | }, 45 | t.data_); 46 | } 47 | 48 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/exp_logical.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace step { 6 | 7 | enum class exp_logical { EXP_TRUE, EXP_FALSE, EXP_UNKNOWN }; 8 | 9 | std::ostream& operator<<(std::ostream&, exp_logical); 10 | 11 | } // namespace step 12 | -------------------------------------------------------------------------------- /step/include/step/has_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace step { 6 | 7 | template 8 | struct has_data : std::false_type {}; 9 | 10 | template 11 | struct has_data().data_)>> 12 | : std::true_type {}; 13 | 14 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/id_t.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace step { 7 | 8 | struct id_t { 9 | static constexpr auto const kInvalid = std::numeric_limits::max(); 10 | id_t() = default; 11 | id_t(unsigned id) : id_{id} {} // NOLINT 12 | static id_t invalid() { return {kInvalid}; } 13 | bool operator!=(id_t const& o) const { return o.id_ != id_; } 14 | bool operator==(id_t const& o) const { return o.id_ == id_; } 15 | bool operator!=(unsigned id) const { return id_ != id; } 16 | bool operator==(unsigned id) const { return id_ == id; } 17 | friend std::ostream& operator<<(std::ostream&, id_t const&); 18 | unsigned id_{kInvalid}; 19 | }; 20 | 21 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/is_collection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace step { 7 | 8 | template 9 | struct is_collection : std::false_type {}; 10 | 11 | template 12 | struct is_collection()))>> 13 | : std::true_type {}; 14 | 15 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "utl/verify.h" 7 | 8 | #include "step/id_t.h" 9 | 10 | namespace step { 11 | 12 | struct root_entity; 13 | 14 | struct model { 15 | template 16 | T const& get_entity(step::id_t const& id) const { 17 | return const_cast(this)->get_entity(id); // NOLINT 18 | } 19 | 20 | template 21 | T& get_entity(step::id_t const& id) { 22 | utl::verify(id_to_entity_.size() > id.id_, "invalid id"); 23 | auto* const entity = dynamic_cast(id_to_entity_[id.id_]); 24 | utl::verify(entity != nullptr, "bad cast"); 25 | return *entity; 26 | } 27 | 28 | template 29 | T& add_entity() { 30 | auto const e = entity_mem_.emplace_back(std::make_unique()).get(); 31 | e->id_ = id_to_entity_.size(); 32 | id_to_entity_.push_back(e); 33 | return *static_cast(e); 34 | } 35 | 36 | std::vector id_to_entity_; 37 | std::vector> entity_mem_; 38 | }; 39 | 40 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/parse_lines.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "utl/enumerate.h" 6 | #include "utl/parser/cstr.h" 7 | 8 | #include "fmt/core.h" 9 | 10 | #include "step/model.h" 11 | #include "step/root_entity.h" 12 | #include "step/split_line.h" 13 | 14 | namespace step { 15 | 16 | template 17 | model parse_lines(Parser const& p, utl::cstr step) { 18 | model m; 19 | for (auto [line_idx, line] : utl::enumerate(utl::lines{step})) { 20 | try { 21 | auto const split = split_line(line); 22 | if (!split.has_value()) { 23 | continue; 24 | } 25 | 26 | auto entity = p.parse(split->name_, split->entity_); 27 | if (!entity.has_value()) { 28 | continue; 29 | } 30 | 31 | auto* const e_ptr = m.entity_mem_.emplace_back(std::move(*entity)).get(); 32 | e_ptr->id_ = split->id_; 33 | if (m.id_to_entity_.size() <= split->id_.id_) { 34 | m.id_to_entity_.resize(split->id_.id_ + 1); 35 | } 36 | m.id_to_entity_[split->id_.id_] = e_ptr; 37 | } catch (std::exception const& e) { 38 | fmt::print("unable to parse line {}: {}\n", line_idx + 1, line.view()); 39 | } 40 | } 41 | for (auto& ptr : m.entity_mem_) { 42 | ptr->resolve(m.id_to_entity_); 43 | } 44 | return m; 45 | } 46 | 47 | } // namespace step 48 | -------------------------------------------------------------------------------- /step/include/step/parse_step.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "boost/algorithm/string.hpp" 7 | 8 | #include "cista/type_hash/type_name.h" 9 | 10 | #include "utl/parser/arg_parser.h" 11 | #include "utl/parser/cstr.h" 12 | #include "utl/verify.h" 13 | 14 | #include "step/exp_logical.h" 15 | #include "step/id_t.h" 16 | #include "step/is_collection.h" 17 | 18 | namespace step { 19 | 20 | template 21 | struct has_resize : std::false_type {}; 22 | 23 | template 24 | struct has_resize().resize(0))>> 25 | : std::true_type {}; 26 | 27 | inline std::optional get_next_token(utl::cstr const s, char token) { 28 | if (s.len == 0) { 29 | return std::nullopt; 30 | } 31 | 32 | auto const* const pos = 33 | static_cast(std::memchr(s.str, token, s.len)); 34 | if (pos == nullptr) { 35 | return std::nullopt; 36 | } 37 | 38 | return utl::cstr{pos + 1, s.len - (pos - s.str) - 1}; 39 | } 40 | 41 | inline void parse_step(utl::cstr& in, id_t& i) { 42 | utl::verify(in.len > 1 && (in[0] == '$' || 43 | (in[0] == '#' && (std::isdigit(in[1]) != 0))), 44 | "expected id, got: {}", in.view()); 45 | if (in[0] == '$') { 46 | ++in; 47 | fmt::print("warning: id set to $, unable to resolve (-> nullptr)\n"); 48 | i.id_ = id_t::kInvalid; 49 | } else { 50 | ++in; 51 | utl::parse_arg(in, i.id_); 52 | } 53 | } 54 | 55 | template 56 | void parse_step(utl::cstr& s, T*& ptr) { 57 | auto id = step::id_t{}; 58 | parse_step(s, id); 59 | ptr = reinterpret_cast(static_cast(id.id_)); 60 | } 61 | 62 | inline void parse_step(utl::cstr& s, double& val) { 63 | char* end = nullptr; 64 | val = std::strtod(s.str, &end); 65 | s.len -= end - s.str; 66 | s.str = end; 67 | } 68 | 69 | template 70 | std::enable_if_t> parse_step(utl::cstr& s, T& val) { 71 | utl::parse_arg(s, val); 72 | } 73 | 74 | inline void parse_step(utl::cstr& s, bool& val) { 75 | utl::verify(s.len > 0 && s[0] == '.', "expected bool, got {}", s.view()); 76 | ++s; 77 | 78 | utl::verify(s.len > 0 && (s[0] == 'T' || s[0] == 'F'), 79 | "expected bool '.T.' or '.F.', got {}", s.view()); 80 | val = s[0] == 'T'; 81 | ++s; 82 | 83 | utl::verify(s.len > 0 && s[0] == '.', "expected bool, got {}", s.view()); 84 | ++s; 85 | } 86 | 87 | inline void parse_step(utl::cstr& s, exp_logical& val) { 88 | utl::verify(s.len > 0 && s[0] == '.', "expected bool, got {}", s.view()); 89 | ++s; 90 | 91 | utl::verify(s.len > 0 && (s[0] == 'T' || s[0] == 'F' || s[0] == 'U'), 92 | "expected logical '.T.', '.F.', or '.U.', got {}", s.view()); 93 | switch (s[0]) { 94 | case 'T': val = exp_logical::EXP_TRUE; 95 | case 'F': val = exp_logical::EXP_FALSE; 96 | case 'U': val = exp_logical::EXP_UNKNOWN; 97 | } 98 | ++s; 99 | 100 | utl::verify(s.len > 0 && s[0] == '.', "expected bool, got {}", s.view()); 101 | ++s; 102 | } 103 | 104 | inline void parse_step(utl::cstr& s, std::string& str) { 105 | utl::verify(s.len > 0 && s[0] == '\'', "string starts with ', got {}", 106 | s.view()); 107 | ++s; 108 | 109 | auto const end = get_next_token(s, '\''); 110 | utl::verify(end.has_value(), "string ends with ', got {}", s.view()); 111 | 112 | str = std::string{s.data(), static_cast(end->data() - s.data() - 1)}; 113 | s = *end; 114 | } 115 | 116 | template 117 | std::enable_if_t::value> parse_step(utl::cstr& s, T& v) { 118 | if (s.len != 0 && s[0] == '$') { // invalid IFC handled gracefully 119 | ++s; 120 | return; 121 | } 122 | 123 | utl::verify(s.len != 0 && s[0] == '(', "set begins with (, got {}", s.view()); 124 | ++s; 125 | auto i = 0U; 126 | while (s.len > 0 && s[0] != ')') { 127 | if constexpr (has_resize::value) { 128 | v.resize(i + 1); 129 | } 130 | parse_step(s, v[i]); 131 | if (s.len > 0 && s[0] == ',') { 132 | ++s; 133 | s = s.skip_whitespace_front(); 134 | } 135 | ++i; 136 | } 137 | utl::verify(s.len != 0 && s[0] == ')', "set ends with ), got {}", 138 | s.len > 0 ? s[0] : '?'); 139 | ++s; 140 | } 141 | 142 | template 143 | std::enable_if_t> parse_step(utl::cstr& s, T& v) {} 144 | 145 | template 146 | void parse_step(utl::cstr& s, std::optional& o) { 147 | utl::verify(s.len > 0, "expected optional {}, got {}", cista::type_str(), 148 | s.view()); 149 | if (s[0] == '$') { 150 | ++s; 151 | o = std::nullopt; 152 | } else { 153 | T arg; 154 | parse_step(s, arg); 155 | o = std::move(arg); 156 | } 157 | } 158 | 159 | } // namespace step 160 | -------------------------------------------------------------------------------- /step/include/step/resolve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "step/has_data.h" 7 | #include "step/is_collection.h" 8 | #include "step/root_entity.h" 9 | 10 | namespace step { 11 | 12 | template 13 | void resolve(std::vector const& v, T*& el) { 14 | auto const id = reinterpret_cast(el); 15 | el = (v.size() <= id) ? nullptr : reinterpret_cast(v[id]); 16 | } 17 | 18 | template 19 | std::enable_if_t::value> resolve( 20 | std::vector const& v, T& vec) { 21 | for (auto& el : vec) { 22 | resolve(v, el); 23 | } 24 | } 25 | 26 | template 27 | std::enable_if_t::value> resolve(std::vector const& v, 28 | T& select) { 29 | select.resolve(v); 30 | } 31 | 32 | template 33 | void resolve(std::vector const& v, std::optional& opt) { 34 | if (opt.has_value()) { 35 | resolve(v, *opt); 36 | } 37 | } 38 | 39 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/root_entity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "step/id_t.h" 9 | 10 | namespace step { 11 | 12 | struct write_context; 13 | 14 | struct root_entity { 15 | root_entity() = default; 16 | root_entity(root_entity const&) = delete; 17 | root_entity(root_entity&&) = delete; 18 | root_entity& operator=(root_entity const&) = delete; 19 | root_entity& operator=(root_entity&&) = delete; 20 | virtual ~root_entity(); 21 | virtual std::string_view name() const = 0; 22 | virtual void resolve(std::vector const&) = 0; 23 | virtual void write(write_context const&, std::ostream&, 24 | bool write_type_name) const = 0; 25 | friend void write(write_context const& ctx, std::ostream& out, 26 | root_entity const& e) { 27 | e.write(ctx, out, true); 28 | } 29 | std::size_t line_idx_{0U}; 30 | id_t id_; 31 | }; 32 | 33 | using entity_ptr = std::unique_ptr; 34 | 35 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/selective_entity_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utl/parser/cstr.h" 9 | 10 | #include "step/root_entity.h" 11 | 12 | namespace step { 13 | 14 | struct selective_entity_parser { 15 | using parser_fn_t = std::function; 16 | using parser_map_t = std::unordered_map; 17 | 18 | std::optional parse(utl::cstr type_name, utl::cstr rest) const { 19 | if (auto const it = parsers_.find(type_name.view()); it != end(parsers_)) { 20 | return it->second(rest); 21 | } 22 | return std::nullopt; 23 | } 24 | 25 | template 26 | void register_parsers() { 27 | (register_parser(), ...); 28 | } 29 | 30 | template 31 | void register_parser() { 32 | parsers_[T::NAME] = [](utl::cstr s) { 33 | auto v = std::make_unique(); 34 | parse_step(s, *v); 35 | return v; 36 | }; 37 | } 38 | 39 | parser_map_t parsers_; 40 | }; 41 | 42 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/split_line.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "utl/parser/cstr.h" 6 | 7 | #include "step/id_t.h" 8 | 9 | namespace step { 10 | 11 | struct line { 12 | step::id_t id_; 13 | utl::cstr name_, entity_; 14 | }; 15 | 16 | std::optional split_line(utl::cstr); 17 | 18 | } // namespace step -------------------------------------------------------------------------------- /step/include/step/write.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "cista/type_hash/type_name.h" 9 | 10 | #include "utl/enumerate.h" 11 | #include "utl/verify.h" 12 | 13 | #include "step/id_t.h" 14 | #include "step/is_collection.h" 15 | 16 | namespace step { 17 | 18 | struct model; 19 | struct root_entity; 20 | 21 | struct write_context { 22 | std::unordered_map ptr_to_id_; 23 | }; 24 | 25 | void write(std::ostream&, model const&); 26 | 27 | template 28 | struct is_comparable : std::false_type {}; 29 | 30 | template 31 | struct is_comparable< 32 | T, std::void_t() == std::declval())>> 33 | : std::true_type {}; 34 | 35 | template 36 | void write(write_context const& ctx, std::ostream& out, T const& e) { 37 | using Type = std::decay_t; 38 | if constexpr (std::is_base_of_v) { 39 | e.write(ctx, out, true); 40 | } else if constexpr (std::is_pointer_v) { 41 | if (e == nullptr) { 42 | out << "*"; 43 | } else { 44 | auto const it = ctx.ptr_to_id_.find(e); 45 | utl::verify(it != end(ctx.ptr_to_id_), "could not resolve {}* {}", 46 | cista::type_str(), static_cast(e)); 47 | out << "#" << it->second.id_; 48 | } 49 | } else if constexpr (std::is_same_v) { 50 | out << '\'' << e << '\''; 51 | } else if constexpr (is_collection::value) { 52 | out << "("; 53 | for (auto const& [i, el] : utl::enumerate(e)) { 54 | if constexpr (is_comparable>::value) { 55 | if (el != decltype(el){}) { 56 | write(ctx, out, el); 57 | if (i != e.size() - 1) { 58 | out << ", "; 59 | } 60 | } 61 | } else { 62 | write(ctx, out, el); 63 | if (i != e.size() - 1) { 64 | out << ", "; 65 | } 66 | } 67 | } 68 | out << ")"; 69 | } else { 70 | out << e; 71 | } 72 | } 73 | 74 | template 75 | void write(write_context const& ctx, std::ostream& out, 76 | std::optional const& e) { 77 | if (e.has_value()) { 78 | write(ctx, out, *e); 79 | } else { 80 | out << "$"; 81 | } 82 | } 83 | 84 | } // namespace step -------------------------------------------------------------------------------- /step/src/exp_logical.cc: -------------------------------------------------------------------------------- 1 | #include "step/exp_logical.h" 2 | 3 | #include 4 | 5 | namespace step { 6 | 7 | std::ostream& operator<<(std::ostream& out, exp_logical const l) { 8 | switch (l) { 9 | case exp_logical::EXP_FALSE: return out << ".F."; 10 | case exp_logical::EXP_TRUE: return out << ".T."; 11 | case exp_logical::EXP_UNKNOWN: [[fallthrough]]; 12 | default: return out << ".U."; 13 | } 14 | } 15 | 16 | } // namespace step 17 | -------------------------------------------------------------------------------- /step/src/id_t.cc: -------------------------------------------------------------------------------- 1 | #include "step/id_t.h" 2 | 3 | #include 4 | 5 | namespace step { 6 | 7 | std::ostream& operator<<(std::ostream& out, id_t const& id) { 8 | return out << '#' << id.id_; 9 | } 10 | 11 | } // namespace step -------------------------------------------------------------------------------- /step/src/root_entity.cc: -------------------------------------------------------------------------------- 1 | #include "step/root_entity.h" 2 | 3 | namespace step { 4 | 5 | root_entity::~root_entity() = default; 6 | 7 | } // namespace step -------------------------------------------------------------------------------- /step/src/split_line.cc: -------------------------------------------------------------------------------- 1 | #include "step/split_line.h" 2 | 3 | #include "step/parse_step.h" 4 | 5 | namespace step { 6 | 7 | std::optional split_line(utl::cstr in) { 8 | if (in.len == 0 || in[0] != '#') { 9 | return std::nullopt; 10 | } 11 | 12 | line output; 13 | parse_step(in, output.id_); 14 | 15 | in = in.skip_whitespace_front(); 16 | utl::verify(in.len > 3 && in[0] == '=', "no '=' found, got {}", in.view()); 17 | 18 | auto const params_end = in.view().find(");"); 19 | utl::verify(params_end != std::string_view::npos, 20 | "end ');' not found, got {}", in.view()); 21 | 22 | in = in.substr(1, utl::size{params_end - 1}); 23 | in = in.skip_whitespace_front(); 24 | 25 | auto const bracket_pos = in.view().find('('); 26 | utl::verify(bracket_pos != std::string_view::npos, 27 | "no bracket '(' found in: {}", in.view()); 28 | 29 | output.name_ = in.substr(0, utl::size{bracket_pos}); 30 | output.entity_ = in.substr(bracket_pos + 1); 31 | return output; 32 | } 33 | 34 | } // namespace step -------------------------------------------------------------------------------- /step/src/write.cc: -------------------------------------------------------------------------------- 1 | #include "step/write.h" 2 | 3 | #include "utl/enumerate.h" 4 | 5 | #include "step/model.h" 6 | #include "step/root_entity.h" 7 | 8 | namespace step { 9 | 10 | void write(std::ostream& out, model const& m) { 11 | write_context ctx; 12 | for (auto const& [i, e] : utl::enumerate(m.entity_mem_)) { 13 | ctx.ptr_to_id_.emplace(e.get(), i); 14 | } 15 | for (auto const& [i, e] : utl::enumerate(m.entity_mem_)) { 16 | out << "#" << i << " = "; 17 | e->write(ctx, out, true); 18 | out << "\n"; 19 | } 20 | } 21 | 22 | } // namespace step -------------------------------------------------------------------------------- /step/test/entry_parser_test.cc: -------------------------------------------------------------------------------- 1 | #include "doctest/doctest.h" 2 | 3 | #include 4 | 5 | #include "step/selective_entity_parser.h" 6 | #include "step/split_line.h" 7 | 8 | #include "IFC2X3/IfcActorSelect.h" 9 | #include "IFC2X3/IfcAxis2Placement3D.h" 10 | #include "IFC2X3/IfcBuildingElementProxy.h" 11 | #include "IFC2X3/IfcCartesianPoint.h" 12 | #include "IFC2X3/IfcDirection.h" 13 | #include "IFC2X3/IfcOwnerHistory.h" 14 | #include "IFC2X3/IfcPropertyListValue.h" 15 | #include "IFC2X3/IfcPropertySingleValue.h" 16 | #include "IFC2X3/IfcSIUnit.h" 17 | #include "IFC2X3/IfcShapeRepresentation.h" 18 | 19 | TEST_CASE("parse product") { 20 | using building_element_proxy = IFC2X3::IfcBuildingElementProxy; 21 | 22 | constexpr auto const* const input = 23 | "#410 = IFCBUILDINGELEMENTPROXY('2K5zlWhbnD_Pplf7Wq7h2T', #2, " 24 | "'Platzhalter:88209840', $, $, #411, #416, 'Tag:88209840', $);"; 25 | 26 | auto const split = step::split_line(input); 27 | REQUIRE(split.has_value()); 28 | CHECK(split->id_ == 410); 29 | 30 | step::selective_entity_parser p; 31 | p.register_parsers(); 32 | auto const entry = p.parse(split->name_, split->entity_); 33 | REQUIRE(entry.has_value()); 34 | REQUIRE(dynamic_cast(entry->get()) != nullptr); 35 | 36 | auto const& bep = *dynamic_cast(entry->get()); 37 | CHECK(bep.GlobalId_ == "2K5zlWhbnD_Pplf7Wq7h2T"); 38 | CHECK(reinterpret_cast(bep.OwnerHistory_) == 2); 39 | 40 | CHECK(!bep.Description_.has_value()); 41 | CHECK(!bep.ObjectType_.has_value()); 42 | 43 | CHECK(bep.ObjectPlacement_.has_value()); 44 | CHECK(reinterpret_cast(*bep.ObjectPlacement_) == 411); 45 | 46 | CHECK(bep.Representation_.has_value()); 47 | CHECK(reinterpret_cast(*bep.Representation_) == 416); 48 | 49 | REQUIRE(bep.Name_.has_value()); 50 | CHECK(*bep.Name_ == "Platzhalter:88209840"); 51 | 52 | REQUIRE(bep.Tag_.has_value()); 53 | CHECK(bep.Tag_ == "Tag:88209840"); 54 | 55 | CHECK(!bep.CompositionType_.has_value()); 56 | 57 | CHECK(bep.name() == "IFCBUILDINGELEMENTPROXY"); 58 | } 59 | 60 | TEST_CASE("parse share representation") { 61 | using shape_representation = IFC2X3::IfcShapeRepresentation; 62 | 63 | constexpr auto const* const input = 64 | "#96944 = IFCSHAPEREPRESENTATION(#20, 'Body', 'MappedRepresentation', " 65 | "(#96933));"; 66 | 67 | auto const split = step::split_line(input); 68 | REQUIRE(split.has_value()); 69 | CHECK(split->id_ == 96944); 70 | step::selective_entity_parser p; 71 | p.register_parser(); 72 | auto const entry = p.parse(split->name_, split->entity_); 73 | REQUIRE(entry.has_value()); 74 | REQUIRE(dynamic_cast(entry->get()) != nullptr); 75 | 76 | auto const& bep = *dynamic_cast(entry->get()); 77 | REQUIRE(bep.RepresentationType_.has_value()); 78 | CHECK(*bep.RepresentationType_ == "MappedRepresentation"); 79 | CHECK(bep.Items_.size() == 1); 80 | CHECK(reinterpret_cast(bep.Items_[0]) == 96933U); 81 | } 82 | 83 | TEST_CASE("parse cartesian point 1") { 84 | using vertex = IFC2X3::IfcCartesianPoint; 85 | 86 | constexpr auto const* const input = 87 | "#5466 = IFCCARTESIANPOINT((-73910.476024,65619.415293,49080.450753));"; 88 | 89 | auto const split = step::split_line(input); 90 | REQUIRE(split.has_value()); 91 | CHECK(split->id_ == 5466); 92 | step::selective_entity_parser p; 93 | p.register_parsers(); 94 | auto const entry = p.parse(split->name_, split->entity_); 95 | REQUIRE(entry.has_value()); 96 | REQUIRE(nullptr != dynamic_cast(entry->get())); 97 | auto const& coords = dynamic_cast(entry->get())->Coordinates_; 98 | CHECK(std::abs(coords[0] - -73910.476024) <= 0.000001); 99 | CHECK(std::abs(coords[1] - 65619.415293) <= 0.000001); 100 | CHECK(std::abs(coords[2] - 49080.450753) <= 0.000001); 101 | } 102 | 103 | TEST_CASE("parse cartesian point 2") { 104 | using vertex = IFC2X3::IfcCartesianPoint; 105 | 106 | constexpr auto const* const input = 107 | "#16783 = IFCCARTESIANPOINT((-29750.345510,68710.165565,53116.953431));"; 108 | 109 | auto const split = step::split_line(input); 110 | REQUIRE(split.has_value()); 111 | CHECK(split->id_ == 16783); 112 | step::selective_entity_parser p; 113 | p.register_parsers(); 114 | auto const entry = p.parse(split->name_, split->entity_); 115 | REQUIRE(entry.has_value()); 116 | REQUIRE(nullptr != dynamic_cast(entry->get())); 117 | auto const& coords = dynamic_cast(entry->get())->Coordinates_; 118 | CHECK(std::abs(coords[0] - -29750.345510) <= 0.000001); 119 | CHECK(std::abs(coords[1] - 68710.165565) <= 0.000001); 120 | CHECK(std::abs(coords[2] - 53116.953431) <= 0.000001); 121 | } 122 | 123 | TEST_CASE("parse direction") { 124 | using direction = IFC2X3::IfcDirection; 125 | 126 | constexpr auto const* const input = 127 | "#5574 = IFCDIRECTION((0.000000,0.000000,1.000000));"; 128 | 129 | auto const split = step::split_line(input); 130 | REQUIRE(split.has_value()); 131 | CHECK(split->id_ == 5574); 132 | step::selective_entity_parser p; 133 | p.register_parsers(); 134 | auto const entry = p.parse(split->name_, split->entity_); 135 | REQUIRE(entry.has_value()); 136 | REQUIRE(nullptr != dynamic_cast(entry->get())); 137 | auto const& coords = dynamic_cast(entry->get())->DirectionRatios_; 138 | CHECK(std::abs(coords[0] - 0.0) <= 0.000001); 139 | CHECK(std::abs(coords[1] - 0.0) <= 0.000001); 140 | CHECK(std::abs(coords[2] - 1.0) <= 0.000001); 141 | } 142 | 143 | TEST_CASE("parse projection") { 144 | using projection = IFC2X3::IfcAxis2Placement3D; 145 | 146 | constexpr auto const* const input = 147 | "#5563 = IFCAXIS2PLACEMENT3D(#5564, #5565, #5566);"; 148 | 149 | auto const split = step::split_line(input); 150 | REQUIRE(split.has_value()); 151 | CHECK(split->id_ == 5563); 152 | step::selective_entity_parser p; 153 | p.register_parsers(); 154 | auto const entry = p.parse(split->name_, split->entity_); 155 | REQUIRE(entry.has_value()); 156 | REQUIRE(nullptr != dynamic_cast(entry->get())); 157 | auto const& proj = *dynamic_cast(entry->get()); 158 | CHECK(reinterpret_cast(proj.Location_) == 5564); 159 | REQUIRE(proj.Axis_.has_value()); 160 | CHECK(reinterpret_cast(*proj.Axis_) == 5565); 161 | REQUIRE(proj.RefDirection_.has_value()); 162 | CHECK(reinterpret_cast(*proj.RefDirection_) == 5566); 163 | } 164 | 165 | TEST_CASE("parse owner history") { 166 | using owner_history = IFC2X3::IfcOwnerHistory; 167 | 168 | constexpr auto const* const input = 169 | "#5=IFCOWNERHISTORY(#8,#9,$,.DELETED.,$,$,$,1591875543);"; 170 | 171 | auto const split = step::split_line(input); 172 | REQUIRE(split.has_value()); 173 | CHECK(split->id_ == 5); 174 | step::selective_entity_parser p; 175 | p.register_parsers(); 176 | auto const entry = p.parse(split->name_, split->entity_); 177 | REQUIRE(entry.has_value()); 178 | REQUIRE(nullptr != dynamic_cast(entry->get())); 179 | auto const* const history = 180 | dynamic_cast(entry->get()); 181 | CHECK(history->ChangeAction_ == IFC2X3::IfcChangeActionEnum::IFC2X3_DELETED); 182 | CHECK(!history->LastModifiedDate_.has_value()); 183 | CHECK(!history->LastModifyingUser_.has_value()); 184 | CHECK(!history->LastModifyingApplication_.has_value()); 185 | CHECK(history->CreationDate_ == 1591875543); 186 | } 187 | 188 | TEST_CASE("parse owner history throws unknown enum value") { 189 | using owner_history = IFC2X3::IfcOwnerHistory; 190 | 191 | constexpr auto const* const input = 192 | "#5=IFCOWNERHISTORY(#8,#9,$,.DELETE.,$,$,$,1591875543);"; 193 | 194 | auto const split = step::split_line(input); 195 | REQUIRE(split.has_value()); 196 | CHECK(split->id_ == 5); 197 | step::selective_entity_parser p; 198 | p.register_parsers(); 199 | CHECK_THROWS(p.parse(split->name_, split->entity_)); 200 | } 201 | 202 | TEST_CASE("parse positive length measure") { 203 | constexpr auto const* const input = "IFCPOSITIVELENGTHMEASURE(86.)"; 204 | 205 | IFC2X3::IfcValue v; 206 | auto s = utl::cstr{input}; 207 | parse_step(s, v); 208 | 209 | CHECK(v.data_.index() == 0); 210 | CHECK(std::get<11>(std::get(v.data_).data_) == 86); 211 | 212 | CHECK(s.empty()); 213 | } 214 | 215 | TEST_CASE("ifc actor select bad id 1") { 216 | constexpr auto const* const input = "IFCPOSITIVELENGTHMEASURE(86.)"; 217 | 218 | IFC2X3::IfcActorSelect v; 219 | auto s = utl::cstr{input}; 220 | CHECK_THROWS(parse_step(s, v)); 221 | } 222 | 223 | TEST_CASE("ifc actor select bad id 2") { 224 | constexpr auto const* const input = "#,"; 225 | 226 | IFC2X3::IfcActorSelect v; 227 | auto s = utl::cstr{input}; 228 | CHECK_THROWS(parse_step(s, v)); 229 | } 230 | 231 | TEST_CASE("ifc actor select good id") { 232 | constexpr auto const* const input = "#123"; 233 | 234 | IFC2X3::IfcActorSelect v; 235 | auto s = utl::cstr{input}; 236 | parse_step(s, v); 237 | 238 | CHECK(v.tmp_id_.id_ == 123); 239 | } 240 | 241 | TEST_CASE("parse property single value") { 242 | using prop_single_value = IFC2X3::IfcPropertySingleValue; 243 | 244 | constexpr auto const* const input = 245 | R"(#564425=IFCPROPERTYSINGLEVALUE('MaterialThickness','',IFCPOSITIVELENGTHMEASURE(86.),$);)"; 246 | 247 | auto const split = step::split_line(input); 248 | REQUIRE(split.has_value()); 249 | CHECK(split->id_ == 564425); 250 | 251 | step::selective_entity_parser p; 252 | p.register_parsers(); 253 | auto const entry = p.parse(split->name_, split->entity_); 254 | REQUIRE(entry.has_value()); 255 | 256 | auto const* const val = dynamic_cast(entry->get()); 257 | REQUIRE(val != nullptr); 258 | REQUIRE(val->NominalValue_.has_value()); 259 | REQUIRE(std::holds_alternative( 260 | val->NominalValue_->data_)); 261 | 262 | auto const& measure_val = 263 | std::get(val->NominalValue_->data_); 264 | CHECK(measure_val.data_.index() == 11); 265 | CHECK(std::get<11>(measure_val.data_) == 86); 266 | } 267 | 268 | TEST_CASE("parse property list value") { 269 | constexpr auto const* const input = 270 | R"(#564427=IFCPROPERTYLISTVALUE('NominalDiameter','',$,$);)"; 271 | 272 | auto const split = step::split_line(input); 273 | REQUIRE(split.has_value()); 274 | CHECK(split->id_ == 564427); 275 | 276 | step::selective_entity_parser p; 277 | p.register_parsers(); 278 | auto const entry = p.parse(split->name_, split->entity_); 279 | 280 | REQUIRE(entry.has_value()); 281 | CHECK(entry->get()->line_idx_ == 0); 282 | } 283 | 284 | TEST_CASE("parse property list value") { 285 | constexpr auto const* const input = 286 | R"(#11=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);)"; 287 | 288 | auto const split = step::split_line(input); 289 | REQUIRE(split.has_value()); 290 | CHECK(split->id_ == 11); 291 | 292 | step::selective_entity_parser p; 293 | p.register_parsers(); 294 | auto const entry = p.parse(split->name_, split->entity_); 295 | 296 | REQUIRE(entry.has_value()); 297 | auto const* const val = dynamic_cast(entry->get()); 298 | REQUIRE(val != nullptr); 299 | CHECK(val->Dimensions_ == nullptr); 300 | CHECK(val->UnitType_ == IFC2X3::IfcUnitEnum::IFC2X3_LENGTHUNIT); 301 | REQUIRE(val->Prefix_.has_value()); 302 | CHECK(*val->Prefix_ == IFC2X3::IfcSIPrefix::IFC2X3_MILLI); 303 | CHECK(val->Name_ == IFC2X3::IfcSIUnitName::IFC2X3_METRE); 304 | } -------------------------------------------------------------------------------- /step/test/main.cc: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest/doctest.h" -------------------------------------------------------------------------------- /step/test/parse_basic_datatypes_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "doctest/doctest.h" 4 | 5 | #include "step/model.h" 6 | #include "step/parse_step.h" 7 | 8 | TEST_CASE("parse basic data types") { 9 | using step::parse_step; 10 | SUBCASE("int") { 11 | SUBCASE("before comma") { 12 | int i{}; 13 | auto s = utl::cstr{"123,"}; 14 | parse_step(s, i); 15 | CHECK(i == 123); 16 | CHECK(s.view() == ","); 17 | } 18 | SUBCASE("before bracket") { 19 | int i{}; 20 | auto s = utl::cstr{"123)"}; 21 | parse_step(s, i); 22 | CHECK(i == 123); 23 | CHECK(s.view() == ")"); 24 | } 25 | } 26 | SUBCASE("double") { 27 | SUBCASE("before comma") { 28 | double d{}; 29 | auto s = utl::cstr{"123,"}; 30 | parse_step(s, d); 31 | CHECK(d == 123); 32 | CHECK(s.view() == ","); 33 | } 34 | SUBCASE("before bracket") { 35 | double d{}; 36 | auto s = utl::cstr{"123)"}; 37 | parse_step(s, d); 38 | CHECK(d == 123); 39 | CHECK(s.view() == ")"); 40 | } 41 | SUBCASE("E notation") { 42 | double d{}; 43 | auto s = utl::cstr{"123E2)"}; 44 | parse_step(s, d); 45 | CHECK(d == 12300); 46 | CHECK(s.view() == ")"); 47 | } 48 | SUBCASE("full test") { 49 | double d{}; 50 | auto s = utl::cstr{"0.12E1)"}; 51 | parse_step(s, d); 52 | CHECK(d == 1.2); 53 | CHECK(s.view() == ")"); 54 | } 55 | } 56 | SUBCASE("bool") { 57 | SUBCASE("true") { 58 | bool b{false}; 59 | auto s = utl::cstr{".T."}; 60 | parse_step(s, b); 61 | CHECK(b == true); 62 | } 63 | SUBCASE("false") { 64 | bool b{true}; 65 | auto s = utl::cstr{".F."}; 66 | parse_step(s, b); 67 | CHECK(b == false); 68 | } 69 | SUBCASE("bad bool") { 70 | SUBCASE("not T or F") { 71 | bool b{true}; 72 | auto s = utl::cstr{".A."}; 73 | CHECK_THROWS(parse_step(s, b)); 74 | } 75 | SUBCASE("missing trailing point") { 76 | bool b{true}; 77 | auto s = utl::cstr{".A"}; 78 | CHECK_THROWS(parse_step(s, b)); 79 | } 80 | SUBCASE("missing leading point") { 81 | bool b{true}; 82 | auto s = utl::cstr{"T."}; 83 | CHECK_THROWS(parse_step(s, b)); 84 | } 85 | } 86 | } 87 | SUBCASE("ptr") { 88 | void* ptr{}; 89 | auto s = utl::cstr{"#123"}; 90 | parse_step(s, ptr); 91 | CHECK(reinterpret_cast(ptr) == 123); 92 | } 93 | SUBCASE("optional") { 94 | SUBCASE("ptr") { 95 | SUBCASE("no value") { 96 | std::optional ptr; 97 | auto s = utl::cstr{"$"}; 98 | parse_step(s, ptr); 99 | CHECK(!ptr.has_value()); 100 | CHECK(s.len == 0); 101 | } 102 | SUBCASE("value") { 103 | std::optional ptr; 104 | auto s = utl::cstr{"#123"}; 105 | parse_step(s, ptr); 106 | REQUIRE(ptr.has_value()); 107 | CHECK(reinterpret_cast(*ptr) == 123); 108 | } 109 | } 110 | SUBCASE("int") { 111 | SUBCASE("no value") { 112 | std::optional i; 113 | auto s = utl::cstr{"$,"}; 114 | parse_step(s, i); 115 | CHECK(s.view() == ","); 116 | CHECK(!i.has_value()); 117 | } 118 | SUBCASE("value") { 119 | std::optional i; 120 | auto s = utl::cstr{"123,"}; 121 | parse_step(s, i); 122 | REQUIRE(i.has_value()); 123 | CHECK(*i == 123); 124 | CHECK(s.view() == ","); 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /step/test/parse_ifc_test.cc: -------------------------------------------------------------------------------- 1 | #include "doctest/doctest.h" 2 | 3 | #include "IFC2X3/IfcColourRgb.h" 4 | #include "IFC2X3/IfcFlowController.h" 5 | #include "IFC2X3/IfcProductRepresentation.h" 6 | #include "IFC2X3/IfcRepresentation.h" 7 | #include "IFC2X3/IfcSite.h" 8 | #include "IFC2X3/IfcSurfaceStyle.h" 9 | #include "IFC2X3/IfcSurfaceStyleRendering.h" 10 | #include "IFC2X3/parser.h" 11 | 12 | std::string ifc_str(std::string const& guid) { 13 | return "#96945 = IFCFLOWCONTROLLER('" + guid + 14 | R"(', #2, 'linDbNetComponent', $, $, #96946, #96951, 'Tag:-281736096'); 15 | #96951 = IFCPRODUCTDEFINITIONSHAPE($, $, (#96944)); 16 | #96944 = IFCSHAPEREPRESENTATION(#20, 'Body', 'MappedRepresentation', (#96933)); 17 | #96933 = IFCMAPPEDITEM(#96927,#96934); 18 | #96935 = IFCDIRECTION((0.000002,1.000000,-0.000000)); 19 | #96936 = IFCDIRECTION((1.000000,-0.000002,0.000000)); 20 | #96937 = IFCDIRECTION((0.000000,-0.000000,-1.000000)); 21 | #96938 = IFCCARTESIANPOINT((-55853.364335,57958.087044,46051.925317)); 22 | #96934 = IFCCARTESIANTRANSFORMATIONOPERATOR3D(#96935,#96936,#96938,1.000000,#96937); 23 | #96927 = IFCREPRESENTATIONMAP(#96929,#96928); 24 | #96930 = IFCCARTESIANPOINT((0.000000,0.000000,0.000000)); 25 | #96931 = IFCDIRECTION((0.000000,0.000000,1.000000)); 26 | #96932 = IFCDIRECTION((1.000000,0.000000,0.000000)); 27 | #96929 = IFCAXIS2PLACEMENT3D(#96930, #96931, #96932); 28 | #96928 = IFCSHAPEREPRESENTATION(#20, 'Body', 'MappedRepresentation', (#92397,#94162,#96919)); 29 | #96919 = IFCMAPPEDITEM(#96913,#96920); 30 | #96913 = IFCREPRESENTATIONMAP(#96915,#96914); 31 | #96914 = IFCSHAPEREPRESENTATION(#20, 'Body', 'Brep', (#94559)); 32 | #94559 = IFCFACETEDBREP(#94560); 33 | #94560 = IFCCLOSEDSHELL((#94561)); 34 | #94561 = IFCFACE((#94562)); 35 | #94562 = IFCFACEOUTERBOUND(#94563, .T.); 36 | #94563 = IFCPOLYLOOP((#94165, #94166, #94167)); 37 | #94165 = IFCCARTESIANPOINT((8.793929,21.230422,105.000056)); 38 | #94166 = IFCCARTESIANPOINT((5.005869,26.779034,104.999912)); 39 | #94167 = IFCCARTESIANPOINT((-0.000000,22.979643,105.000056)); 40 | )"; 41 | } 42 | 43 | TEST_CASE("parse ifc") { 44 | auto const ifc_input = ifc_str("0Gkk91VZX968DF0GjbXoN4"); 45 | auto model = IFC2X3::parse(ifc_input); 46 | auto const& flow_ctrl = 47 | model.get_entity(step::id_t{96945}); 48 | CHECK(flow_ctrl.GlobalId_ == "0Gkk91VZX968DF0GjbXoN4"); 49 | REQUIRE(flow_ctrl.Representation_.has_value()); 50 | auto const& repr = flow_ctrl.Representation_.value(); 51 | REQUIRE(repr->Representations_.size() == 1); 52 | REQUIRE(repr->Representations_.at(0)->RepresentationType_.has_value()); 53 | CHECK(repr->Representations_.at(0)->RepresentationType_.value() == 54 | "MappedRepresentation"); 55 | } 56 | 57 | TEST_CASE("parse id select") { 58 | constexpr auto const* const ifc_input = 59 | R"(#200158=IFCCOLOURRGB($,0.200000,0.200000,0.200000); 60 | #200159=IFCSURFACESTYLERENDERING(#200158,$,$,$,$,$,$,$,.METAL.); 61 | #200160=IFCSURFACESTYLE('Default Surface',.BOTH.,(#200159)); 62 | #200161=IFCPRESENTATIONSTYLEASSIGNMENT((#200160));)"; 63 | 64 | auto model = IFC2X3::parse(ifc_input); 65 | auto const& surface_style = model.get_entity(200160); 66 | REQUIRE(surface_style.Styles_.at(0).data_.index() == 0U); 67 | 68 | auto* const shading = std::get<0>(surface_style.Styles_.at(0).data_); 69 | REQUIRE(shading->SurfaceColour_ != nullptr); 70 | CHECK(shading->SurfaceColour_->Red_ == 0.2); 71 | CHECK(shading->SurfaceColour_->Green_ == 0.2); 72 | CHECK(shading->SurfaceColour_->Blue_ == 0.2); 73 | } 74 | 75 | TEST_CASE("does not define ContainsElements Parameter") { 76 | constexpr auto const* const input = 77 | R"(#22=IFCSITE('21yqcJ2bbAHBf6yeMkCLmK',#5,'Site','Site',$,#23,$,$,.ELEMENT.,$,$,$,$,$);)"; 78 | 79 | auto model = IFC2X3::parse(input); 80 | auto const& site = model.get_entity(22); 81 | CHECK(site.GlobalId_ == "21yqcJ2bbAHBf6yeMkCLmK"); 82 | } 83 | 84 | TEST_CASE("defines ContainsElements Parameter") { 85 | constexpr auto const* const input = 86 | R"(#23 = IFCSITE('2xNM1YvyH50w3CkBOfaqX1', #2, 'Default Site', 'Description of Default Site', $, #24, $, $, .ELEMENT., (24, 28, 0), (54, 25, 0), $, $, $);)"; 87 | 88 | auto model = IFC2X3::parse(input); 89 | auto const& site = model.get_entity(23); 90 | CHECK(site.id_ == 23); 91 | CHECK(site.GlobalId_ == "2xNM1YvyH50w3CkBOfaqX1"); 92 | CHECK(site.RefLatitude_ == std::vector{24, 28, 0}); 93 | CHECK(site.RefLongitude_ == std::vector{54, 25, 0}); 94 | } -------------------------------------------------------------------------------- /step/test/writer_test.cc: -------------------------------------------------------------------------------- 1 | #include "doctest/doctest.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "step/write.h" 7 | 8 | #include "IFC2X3/IfcActionSourceTypeEnum.h" 9 | #include "IFC2X3/IfcCartesianPoint.h" 10 | #include "IFC2X3/IfcSurfaceStyle.h" 11 | #include "IFC2X3/parser.h" 12 | 13 | TEST_CASE("write enum test") { 14 | std::stringstream ss; 15 | write(step::write_context{}, ss, 16 | IFC2X3::IfcActionSourceTypeEnum::IFC2X3_DEAD_LOAD_G); 17 | CHECK(ss.str() == ".DEAD_LOAD_G."); 18 | } 19 | 20 | TEST_CASE("write entity test") { 21 | std::stringstream ss; 22 | IFC2X3::IfcCartesianPoint p; 23 | p.Coordinates_ = {1, 2, 3}; 24 | write(step::write_context{}, ss, p); 25 | CHECK(ss.str() == "IFCCARTESIANPOINT((1, 2, 3));"); 26 | } 27 | 28 | TEST_CASE("write model with references test") { 29 | constexpr auto const* const ifc_input = 30 | R"(#200158=IFCCOLOURRGB($,0.200000,0.200000,0.200000); 31 | #200159=IFCSURFACESTYLERENDERING(#200158,$,$,$,$,$,$,$,.METAL.); 32 | #200160=IFCSURFACESTYLE('Default Surface',.BOTH.,(#200159));)"; 33 | 34 | std::stringstream ss; 35 | write(ss, IFC2X3::parse(ifc_input)); 36 | auto const matches = ss.str() == R"(#0 = IFCCOLOURRGB($, 0.2, 0.2, 0.2); 37 | #1 = IFCSURFACESTYLERENDERING(#0, $, $, $, $, $, $, $, .METAL.); 38 | #2 = IFCSURFACESTYLE('Default Surface', .BOTH., (#1)); 39 | )"; 40 | CHECK(matches); 41 | } 42 | 43 | TEST_CASE("write model with select value test") { 44 | constexpr auto const* const ifc_input = 45 | R"(#564425=IFCPROPERTYSINGLEVALUE('MaterialThickness','',IFCPOSITIVELENGTHMEASURE(86.),$);)"; 46 | 47 | std::stringstream ss; 48 | write(ss, IFC2X3::parse(ifc_input)); 49 | auto const matches = ss.str() == 50 | "#0 = IFCPROPERTYSINGLEVALUE('MaterialThickness', '', " 51 | "IFCPOSITIVELENGTHMEASURE(86), $);\n"; 52 | CHECK(matches); 53 | } 54 | 55 | TEST_CASE("write model with select value test") { 56 | constexpr auto const* const ifc_input = 57 | R"(#11=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);)"; 58 | 59 | std::stringstream ss; 60 | write(ss, IFC2X3::parse(ifc_input)); 61 | auto const matches = 62 | ss.str() == "#0 = IFCSIUNIT(*, .LENGTHUNIT., .MILLI., .METRE.);\n"; 63 | CHECK(matches); 64 | } -------------------------------------------------------------------------------- /tools/buildcache-clang-tidy.lua: -------------------------------------------------------------------------------- 1 | -- match(.*cmake.*) 2 | 3 | ------------------------------------------------------------------------------- 4 | -- This is a re-implementation of the C++ class gcc_wrapper_t. 5 | ------------------------------------------------------------------------------- 6 | 7 | require_std("io") 8 | require_std("os") 9 | require_std("string") 10 | require_std("table") 11 | require_std("bcache") 12 | 13 | 14 | ------------------------------------------------------------------------------- 15 | -- Internal helper functions. 16 | ------------------------------------------------------------------------------- 17 | 18 | local function make_preprocessor_cmd (args, preprocessed_file) 19 | local preprocess_args = {} 20 | 21 | -- Drop arguments that we do not want/need. 22 | local drop_next_arg = false 23 | for i, arg in ipairs(args) do 24 | local drop_this_arg = drop_next_arg 25 | drop_next_arg = false 26 | if arg == "-c" then 27 | drop_this_arg = true 28 | elseif arg == "-o" then 29 | drop_this_arg = true 30 | drop_next_arg = true 31 | end 32 | if not drop_this_arg and i > 6 then 33 | table.insert(preprocess_args, arg) 34 | end 35 | end 36 | 37 | -- Append the required arguments for producing preprocessed output. 38 | table.insert(preprocess_args, "-E") 39 | table.insert(preprocess_args, "-P") 40 | table.insert(preprocess_args, "-o") 41 | table.insert(preprocess_args, preprocessed_file) 42 | 43 | return preprocess_args 44 | end 45 | 46 | local function is_source_file (path) 47 | local ext = bcache.get_extension(path):lower() 48 | return (ext == ".cpp") or (ext == ".cc") or (ext == ".cxx") or (ext == ".c") 49 | end 50 | 51 | 52 | ------------------------------------------------------------------------------- 53 | -- Wrapper interface implementation. 54 | ------------------------------------------------------------------------------- 55 | 56 | function get_capabilities () 57 | -- We can use hard links with GCC since it will never overwrite already 58 | -- existing files. 59 | return { "hard_links" } 60 | end 61 | 62 | function preprocess_source () 63 | local expected = {"-E", "__run_co_compile", "--tidy", "--source", "--"} 64 | for i, arg in ipairs(expected) do 65 | if string.sub(ARGS[i+1], 1, #expected[i]) ~= expected[i] then 66 | error("Unexpected argument " .. i .. ": " .. ARGS[i+1] .. " -> '" .. string.sub(ARGS[i+1], 1, 1 + #expected[i]) .. "' != '" .. expected[i] .. "'") 67 | end 68 | end 69 | 70 | -- Check if this is a compilation command that we support. 71 | local is_object_compilation = false 72 | local has_object_output = false 73 | for i, arg in ipairs(ARGS) do 74 | if arg == "-c" then 75 | is_object_compilation = true 76 | elseif arg == "-o" then 77 | has_object_output = true 78 | elseif arg:sub(1, 1) == "@" then 79 | error("Response files are currently not supported.") 80 | end 81 | end 82 | if (not is_object_compilation) or (not has_object_output) then 83 | error("Unsupported complation command.") 84 | end 85 | 86 | -- Run the preprocessor step. 87 | local preprocessed_file = os.tmpname() 88 | local preprocessor_args = make_preprocessor_cmd(ARGS, preprocessed_file) 89 | 90 | local result = bcache.run(preprocessor_args) 91 | if result.return_code ~= 0 then 92 | os.remove(preprocessed_file) 93 | error("Preprocessing command was unsuccessful.") 94 | end 95 | 96 | -- Read and return the preprocessed file. 97 | local f = assert(io.open(preprocessed_file, "rb")) 98 | local preprocessed_source = f:read("*all") 99 | f:close() 100 | os.remove(preprocessed_file) 101 | 102 | return preprocessed_source 103 | end 104 | 105 | function get_relevant_arguments () 106 | local filtered_args = {} 107 | 108 | -- The first argument is the compiler binary without the path. 109 | table.insert(filtered_args, bcache.get_file_part(ARGS[1])) 110 | 111 | -- Note: We always skip the first arg since we have handled it already. 112 | local skip_next_arg = true 113 | for i, arg in ipairs(ARGS) do 114 | if not skip_next_arg then 115 | -- Does this argument specify a file (we don't want to hash those). 116 | local is_arg_plus_file_name = (arg == "-I") or (arg == "-MF") or 117 | (arg == "-MT") or (arg == "-MQ") or 118 | (arg == "-o") 119 | 120 | -- Generally unwanted argument (things that will not change how we go 121 | -- from preprocessed code to binary object files)? 122 | local first_two_chars = arg:sub(1, 2) 123 | local is_unwanted_arg = (first_two_chars == "-I") or 124 | (first_two_chars == "-D") or 125 | (first_two_chars == "-M") or 126 | is_source_file(arg) 127 | 128 | if is_arg_plus_file_name then 129 | skip_next_arg = true 130 | elseif not is_unwanted_arg then 131 | table.insert(filtered_args, arg) 132 | end 133 | else 134 | skip_next_arg = false 135 | end 136 | end 137 | 138 | return filtered_args 139 | end 140 | 141 | function get_program_id () 142 | -- TODO(m): Add things like executable file size too. 143 | 144 | -- Get the version string for the compiler. 145 | local result = bcache.run({ARGS[1], "--version"}) 146 | if result.return_code ~= 0 then 147 | error("Unable to get the compiler version information string.") 148 | end 149 | 150 | return result.std_out 151 | end 152 | 153 | function get_build_files () 154 | local files = {} 155 | local found_object_file = false 156 | for i = 2, #ARGS do 157 | local next_idx = i + 1 158 | if (ARGS[i] == "-o") and (next_idx <= #ARGS) then 159 | if found_object_file then 160 | error("Only a single target object file can be specified.") 161 | end 162 | files["object"] = ARGS[next_idx] 163 | found_object_file = true 164 | elseif (ARGS[i] == "-ftest-coverage") then 165 | error("Code coverage data is currently not supported.") 166 | end 167 | end 168 | if not found_object_file then 169 | error("Unable to get the target object file.") 170 | end 171 | return files 172 | end -------------------------------------------------------------------------------- /tools/test_dir.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define TEST_EXECUTION_DIR "@CMAKE_CURRENT_SOURCE_DIR@" --------------------------------------------------------------------------------