/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * 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 = 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 ()++; }