diff --git a/CMakeLists.txt b/CMakeLists.txt index 253952e6..d7add659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,6 +199,8 @@ list ( coord/simd_neon.hpp coord/store.hpp coord/traits.hpp + cpp.cpp + cpp.hpp cpuid.cpp cpuid.hpp cpuid_x86.cpp @@ -551,6 +553,10 @@ if (TESTS) target_include_directories(util_${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) add_test(NAME util_${name} COMMAND util_${name}) endforeach(t) + + configure_file (test/cpp.sh.in util_test_cpp.sh @ONLY) + add_test (NAME util_test_cpp COMMAND util_test_cpp.sh) + set_property (TEST util_test_cpp APPEND PROPERTY DEPENDS util_macro) endif () diff --git a/cpp.cpp b/cpp.cpp new file mode 100644 index 00000000..a9660668 --- /dev/null +++ b/cpp.cpp @@ -0,0 +1,171 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2018 Danny Robson + */ + + +/////////////////////////////////////////////////////////////////////////////// +#include "cpp.hpp" + +#include "io.hpp" +#include "cast.hpp" + +using util::cpp::include; +using util::cpp::passthrough; +using util::cpp::processor; + + +/////////////////////////////////////////////////////////////////////////////// +processor::processor () +{ + m_directives.insert ({ + "include", + std::make_unique (*this) + }); +} + + +//----------------------------------------------------------------------------- +void +processor::add (std::string token, std::unique_ptr handler) +{ + m_directives.emplace (std::pair {std::move (token), std::move (handler)}); +}; + + +//----------------------------------------------------------------------------- +void +processor::process (std::ostream &os, const std::experimental::filesystem::path &src) const +{ + const auto data = util::slurp (src); + context ctx; + ctx.source.push (src); + + util::tokeniser tok (data, '\n'); + process (os, ctx, util::view (tok.begin (), tok.end ())); +} + + +//----------------------------------------------------------------------------- +util::tokeniser::iterator +processor::process ( + std::ostream &os, + context &ctx, + util::view::iterator> lines) const +{ + for (auto l = lines.begin (), last = lines.end (); l != last; ++l) { + if (l->empty () || (*l)[0] != '#') { + os << *l << '\n'; + continue; + } + + auto tokens = util::tokeniser (*l, ' '); + auto head = tokens.begin (); + auto head_string = std::string { head->begin () + 1, head->size () - 1 }; + + if (*head == "#endif") + return l; + + + if (*head == "#define") { + auto key = head; + ++key; + + if (key == tokens.end ()) + throw std::runtime_error ("expected token for define"); + + std::string key_string { key->begin (), key->size () }; + + auto val = key; + ++val; + ctx.defines[key_string] = val == tokens.end () ? "" : std::string (val->begin (), val->size ()); + continue; + } + + if (*head == "#ifndef") { + auto tail = head + 1; + if (tail == tokens.end ()) + throw std::runtime_error ("expected token for define"); + + // recurse and parse the block... + std::string name { tail->begin (), tail->end () }; + if (ctx.defines.find (name) == ctx.defines.cend ()) { + l = process (os, ctx, {++l, lines.end ()}); + // ... or skip until and endif + } else { + for (++l; l != lines.end () && *l != "#endif"; ++l) + ; + } + + // check if we've got the expected endif + if (l == lines.cend () || *l != "#endif") + throw std::runtime_error ("expected #endif"); + + continue; + } + + auto handler = m_directives.find (head_string); + if (handler == m_directives.end ()) + throw unknown_directive (head_string); + + lines.consume (handler->second->process (os, ctx, {l, lines.end()})); + } + + return lines.end (); +} + + +/////////////////////////////////////////////////////////////////////////////// +passthrough::passthrough (const std::string &name): + m_name (name) +{ ; } + + +//----------------------------------------------------------------------------- +util::tokeniser::iterator +passthrough::process (std::ostream &os, + context&, + util::view::iterator> lines) const +{ + os << *lines.begin () << '\n'; + return lines.begin ()++; +} + + +/////////////////////////////////////////////////////////////////////////////// +include::include (processor &_parent): + m_parent (_parent) +{ ; } + + +//----------------------------------------------------------------------------- +util::tokeniser::iterator +include::process (std::ostream &os, + context &ctx, + util::view::iterator> lines) const +{ + const auto name = lines.begin ()->slice (strlen("#include '"), -2); + std::experimental::filesystem::path fragment { name.begin (), name.end () }; + + const auto target = ctx.source.top ().parent_path () / fragment; + const auto data = util::slurp (target); + + ctx.source.push (target); + util::tokeniser tok (data, '\n'); + m_parent.process (os, ctx, util::view (tok.begin (), tok.end ())); + + ctx.source.pop (); + + return lines.begin ()++; +} diff --git a/cpp.hpp b/cpp.hpp new file mode 100644 index 00000000..6ac99b76 --- /dev/null +++ b/cpp.hpp @@ -0,0 +1,119 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2018 Danny Robson + */ + +#ifndef CRUFT_UTIL_CPP_HPP +#define CRUFT_UTIL_CPP_HPP + +#include "string.hpp" +#include "view.hpp" + +#include +#include +#include +#include +#include + +namespace util::cpp { + /////////////////////////////////////////////////////////////////////////// + struct context { + std::stack source; + std::map defines; + }; + + + /////////////////////////////////////////////////////////////////////////// + class directive { + public: + virtual ~directive () = default; + + virtual util::tokeniser::iterator + process (std::ostream&, + context&, + util::view::iterator>) const = 0; + }; + + + /////////////////////////////////////////////////////////////////////////// + class processor { + public: + processor (); + void add (std::string token, std::unique_ptr); + + void process (std::ostream&, const std::experimental::filesystem::path&) const; + + std::experimental::filesystem::path + resolve (const std::experimental::filesystem::path&) const; + + util::tokeniser::iterator + process (std::ostream&, + context&, + util::view::iterator>) const; + + private: + std::map> m_directives; + }; + + + /////////////////////////////////////////////////////////////////////////// + class unknown_directive : public std::runtime_error { + using runtime_error::runtime_error; + }; + + + /////////////////////////////////////////////////////////////////////////// + class ignore final : public directive { + virtual util::tokeniser::iterator + process (std::ostream&, + context&, + util::view::iterator> lines) const override + { return lines.begin ()++; } + }; + + + //------------------------------------------------------------------------- + class passthrough final : public directive { + public: + passthrough (const std::string &name); + + virtual util::tokeniser::iterator + process (std::ostream&, + context&, + util::view::iterator>) const override; + + private: + const std::string m_name; + }; + + + //------------------------------------------------------------------------- + class include : public directive { + public: + include (processor &_parent); + + void add (const std::experimental::filesystem::path&); + + virtual util::tokeniser::iterator + process (std::ostream&, + context&, + util::view::iterator>) const override; + + private: + processor &m_parent; + std::vector m_paths; + }; +}; + +#endif diff --git a/test/cpp.sh.in b/test/cpp.sh.in new file mode 100755 index 00000000..88bf6e3b --- /dev/null +++ b/test/cpp.sh.in @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +CPP="@CMAKE_CURRENT_BINARY_DIR@/macro" + +count=0 +ret=0 + +for src in "@CMAKE_CURRENT_SOURCE_DIR@/test/cpp/good"/*.inc; do + res="${src%.inc}.res" + + if cmp --quiet <($CPP ${src}) <(cat ${res}); then + head="ok" + else + head="not ok" + ret=1 + fi + + echo "${head} - ${src}" + count=$((count+1)) +done + +echo "1..${count}" +exit $ret diff --git a/test/cpp/good/0000_empty.inc b/test/cpp/good/0000_empty.inc new file mode 100644 index 00000000..e69de29b diff --git a/test/cpp/good/0000_empty.res b/test/cpp/good/0000_empty.res new file mode 100644 index 00000000..e69de29b diff --git a/test/cpp/good/0001_one_word.inc b/test/cpp/good/0001_one_word.inc new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/test/cpp/good/0001_one_word.inc @@ -0,0 +1 @@ +foo diff --git a/test/cpp/good/0001_one_word.res b/test/cpp/good/0001_one_word.res new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/test/cpp/good/0001_one_word.res @@ -0,0 +1 @@ +foo diff --git a/test/cpp/good/1000_relative_include.inc b/test/cpp/good/1000_relative_include.inc new file mode 100644 index 00000000..4b7bf801 --- /dev/null +++ b/test/cpp/good/1000_relative_include.inc @@ -0,0 +1 @@ +#include "0000_empty.inc" diff --git a/test/cpp/good/1000_relative_include.res b/test/cpp/good/1000_relative_include.res new file mode 100644 index 00000000..e69de29b diff --git a/test/cpp/good/2000_header_guard.inc b/test/cpp/good/2000_header_guard.inc new file mode 100644 index 00000000..4501a2e1 --- /dev/null +++ b/test/cpp/good/2000_header_guard.inc @@ -0,0 +1,2 @@ +#include "guarded.hpp" +#include "guarded.hpp" diff --git a/test/cpp/good/2000_header_guard.res b/test/cpp/good/2000_header_guard.res new file mode 100644 index 00000000..ba578e48 --- /dev/null +++ b/test/cpp/good/2000_header_guard.res @@ -0,0 +1 @@ +BAR diff --git a/test/cpp/good/guarded.hpp b/test/cpp/good/guarded.hpp new file mode 100644 index 00000000..5930c806 --- /dev/null +++ b/test/cpp/good/guarded.hpp @@ -0,0 +1,4 @@ +#ifndef FOO +#define FOO +BAR +#endif diff --git a/tools/macro.cpp b/tools/macro.cpp index 78890c7e..01306569 100644 --- a/tools/macro.cpp +++ b/tools/macro.cpp @@ -2,6 +2,7 @@ #include "iterator.hpp" #include "string.hpp" #include "view.hpp" +#include "cpp.hpp" #include #include @@ -17,6 +18,7 @@ enum { }; +#if 0 /////////////////////////////////////////////////////////////////////////////// void process (std::ostream &dst, const std::experimental::filesystem::path&); @@ -74,4 +76,19 @@ main (const int argc, const char **argv) } else { process (std::cout, src); }; -}; \ No newline at end of file +}; + +#endif + + +int +main (const int argc, const char **argv) +{ + if (argc < 2) { + std::cerr << argv[ARG_SELF] << " \n"; + return EXIT_FAILURE; + } + + util::cpp::processor cpp; + cpp.process (std::cout, argv[ARG_SRC]); +} \ No newline at end of file