cpp: add basic C preprocessor emulator
This commit is contained in:
parent
49ebbec37f
commit
de247c7e7b
@ -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 ()
|
||||
|
||||
|
||||
|
171
cpp.cpp
Normal file
171
cpp.cpp
Normal file
@ -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 <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#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<include> (*this)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void
|
||||
processor::add (std::string token, std::unique_ptr<directive> 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<char> (src);
|
||||
context ctx;
|
||||
ctx.source.push (src);
|
||||
|
||||
util::tokeniser<const char*> tok (data, '\n');
|
||||
process (os, ctx, util::view (tok.begin (), tok.end ()));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
util::tokeniser<const char*>::iterator
|
||||
processor::process (
|
||||
std::ostream &os,
|
||||
context &ctx,
|
||||
util::view<util::tokeniser<const char*>::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<const char*>::iterator
|
||||
passthrough::process (std::ostream &os,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::iterator> lines) const
|
||||
{
|
||||
os << *lines.begin () << '\n';
|
||||
return lines.begin ()++;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
include::include (processor &_parent):
|
||||
m_parent (_parent)
|
||||
{ ; }
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
util::tokeniser<const char*>::iterator
|
||||
include::process (std::ostream &os,
|
||||
context &ctx,
|
||||
util::view<util::tokeniser<const char*>::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<char> (target);
|
||||
|
||||
ctx.source.push (target);
|
||||
util::tokeniser<const char*> tok (data, '\n');
|
||||
m_parent.process (os, ctx, util::view (tok.begin (), tok.end ()));
|
||||
|
||||
ctx.source.pop ();
|
||||
|
||||
return lines.begin ()++;
|
||||
}
|
119
cpp.hpp
Normal file
119
cpp.hpp
Normal file
@ -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 <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#ifndef CRUFT_UTIL_CPP_HPP
|
||||
#define CRUFT_UTIL_CPP_HPP
|
||||
|
||||
#include "string.hpp"
|
||||
#include "view.hpp"
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <stack>
|
||||
|
||||
namespace util::cpp {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
struct context {
|
||||
std::stack<std::experimental::filesystem::path> source;
|
||||
std::map<std::string,std::string> defines;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
class directive {
|
||||
public:
|
||||
virtual ~directive () = default;
|
||||
|
||||
virtual util::tokeniser<const char*>::iterator
|
||||
process (std::ostream&,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::iterator>) const = 0;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
class processor {
|
||||
public:
|
||||
processor ();
|
||||
void add (std::string token, std::unique_ptr<directive>);
|
||||
|
||||
void process (std::ostream&, const std::experimental::filesystem::path&) const;
|
||||
|
||||
std::experimental::filesystem::path
|
||||
resolve (const std::experimental::filesystem::path&) const;
|
||||
|
||||
util::tokeniser<const char*>::iterator
|
||||
process (std::ostream&,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::iterator>) const;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<directive>> m_directives;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
class unknown_directive : public std::runtime_error {
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
class ignore final : public directive {
|
||||
virtual util::tokeniser<const char*>::iterator
|
||||
process (std::ostream&,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::iterator> lines) const override
|
||||
{ return lines.begin ()++; }
|
||||
};
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
class passthrough final : public directive {
|
||||
public:
|
||||
passthrough (const std::string &name);
|
||||
|
||||
virtual util::tokeniser<const char*>::iterator
|
||||
process (std::ostream&,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::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<const char*>::iterator
|
||||
process (std::ostream&,
|
||||
context&,
|
||||
util::view<util::tokeniser<const char*>::iterator>) const override;
|
||||
|
||||
private:
|
||||
processor &m_parent;
|
||||
std::vector<std::experimental::filesystem::path> m_paths;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
23
test/cpp.sh.in
Executable file
23
test/cpp.sh.in
Executable file
@ -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
|
0
test/cpp/good/0000_empty.inc
Normal file
0
test/cpp/good/0000_empty.inc
Normal file
0
test/cpp/good/0000_empty.res
Normal file
0
test/cpp/good/0000_empty.res
Normal file
1
test/cpp/good/0001_one_word.inc
Normal file
1
test/cpp/good/0001_one_word.inc
Normal file
@ -0,0 +1 @@
|
||||
foo
|
1
test/cpp/good/0001_one_word.res
Normal file
1
test/cpp/good/0001_one_word.res
Normal file
@ -0,0 +1 @@
|
||||
foo
|
1
test/cpp/good/1000_relative_include.inc
Normal file
1
test/cpp/good/1000_relative_include.inc
Normal file
@ -0,0 +1 @@
|
||||
#include "0000_empty.inc"
|
0
test/cpp/good/1000_relative_include.res
Normal file
0
test/cpp/good/1000_relative_include.res
Normal file
2
test/cpp/good/2000_header_guard.inc
Normal file
2
test/cpp/good/2000_header_guard.inc
Normal file
@ -0,0 +1,2 @@
|
||||
#include "guarded.hpp"
|
||||
#include "guarded.hpp"
|
1
test/cpp/good/2000_header_guard.res
Normal file
1
test/cpp/good/2000_header_guard.res
Normal file
@ -0,0 +1 @@
|
||||
BAR
|
4
test/cpp/good/guarded.hpp
Normal file
4
test/cpp/good/guarded.hpp
Normal file
@ -0,0 +1,4 @@
|
||||
#ifndef FOO
|
||||
#define FOO
|
||||
BAR
|
||||
#endif
|
@ -2,6 +2,7 @@
|
||||
#include "iterator.hpp"
|
||||
#include "string.hpp"
|
||||
#include "view.hpp"
|
||||
#include "cpp.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
@ -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);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
int
|
||||
main (const int argc, const char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
std::cerr << argv[ARG_SELF] << " <src>\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
util::cpp::processor cpp;
|
||||
cpp.process (std::cout, argv[ARG_SRC]);
|
||||
}
|
Loading…
Reference in New Issue
Block a user