forked from standardese/cppast
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cpp
335 lines (305 loc) · 13.3 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors
// SPDX-License-Identifier: MIT
#include <iostream>
#include <cxxopts.hpp>
#include <cppast/code_generator.hpp> // for generate_code()
#include <cppast/cpp_entity_kind.hpp> // for the cpp_entity_kind definition
#include <cppast/cpp_forward_declarable.hpp> // for is_definition()
#include <cppast/cpp_namespace.hpp> // for cpp_namespace
#include <cppast/libclang_parser.hpp> // for libclang_parser, libclang_compile_config, cpp_entity,...
#include <cppast/visitor.hpp> // for visit()
// print help options
void print_help(const cxxopts::Options& options)
{
std::cout << options.help({"", "compilation"}) << '\n';
}
// print error message
void print_error(const std::string& msg)
{
std::cerr << msg << '\n';
}
// prints the AST entry of a cpp_entity (base class for all entities),
// will only print a single line
void print_entity(std::ostream& out, const cppast::cpp_entity& e)
{
// print name and the kind of the entity
if (!e.name().empty())
out << e.name();
else
out << "<anonymous>";
out << " (" << cppast::to_string(e.kind()) << ")";
// print whether or not it is a definition
if (cppast::is_definition(e))
out << " [definition]";
// print number of attributes
if (!e.attributes().empty())
out << " [" << e.attributes().size() << " attribute(s)]";
if (e.kind() == cppast::cpp_entity_kind::language_linkage_t)
// no need to print additional information for language linkages
out << '\n';
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
// cast to cpp_namespace
auto& ns = static_cast<const cppast::cpp_namespace&>(e);
// print whether or not it is inline
if (ns.is_inline())
out << " [inline]";
out << '\n';
}
else
{
// print the declaration of the entity
// it will only use a single line
// derive from code_generator and implement various callbacks for printing
// it will print into a std::string
class code_generator : public cppast::code_generator
{
std::string str_; // the result
bool was_newline_ = false; // whether or not the last token was a newline
// needed for lazily printing them
public:
code_generator(const cppast::cpp_entity& e)
{
// kickoff code generation here
cppast::generate_code(*this, e);
}
// return the result
const std::string& str() const noexcept
{
return str_;
}
private:
// called to retrieve the generation options of an entity
generation_options do_get_options(const cppast::cpp_entity&,
cppast::cpp_access_specifier_kind) override
{
// generate declaration only
return code_generator::declaration;
}
// no need to handle indentation, as only a single line is used
void do_indent() override {}
void do_unindent() override {}
// called when a generic token sequence should be generated
// there are specialized callbacks for various token kinds,
// to e.g. implement syntax highlighting
void do_write_token_seq(cppast::string_view tokens) override
{
if (was_newline_)
{
// lazily append newline as space
str_ += ' ';
was_newline_ = false;
}
// append tokens
str_ += tokens.c_str();
}
// called when a newline should be generated
// we're lazy as it will always generate a trailing newline,
// we don't want
void do_write_newline() override
{
was_newline_ = true;
}
} generator(e);
// print generated code
out << ": `" << generator.str() << '`' << '\n';
}
}
// prints the AST of a file
void print_ast(std::ostream& out, const cppast::cpp_file& file)
{
// print file name
out << "AST for '" << file.name() << "':\n";
std::string prefix; // the current prefix string
// recursively visit file and all children
cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
if (e.kind() == cppast::cpp_entity_kind::file_t || cppast::is_templated(e)
|| cppast::is_friended(e))
// no need to do anything for a file,
// templated and friended entities are just proxies, so skip those as well
// return true to continue visit for children
return true;
else if (info.event == cppast::visitor_info::container_entity_exit)
{
// we have visited all children of a container,
// remove prefix
prefix.pop_back();
prefix.pop_back();
}
else
{
out << prefix; // print prefix for previous entities
// calculate next prefix
if (info.last_child)
{
if (info.event == cppast::visitor_info::container_entity_enter)
prefix += " ";
out << "+-";
}
else
{
if (info.event == cppast::visitor_info::container_entity_enter)
prefix += "| ";
out << "|-";
}
print_entity(out, e);
}
return true;
});
}
// parse a file
std::unique_ptr<cppast::cpp_file> parse_file(const cppast::libclang_compile_config& config,
const cppast::diagnostic_logger& logger,
const std::string& filename, bool fatal_error)
{
// the entity index is used to resolve cross references in the AST
// we don't need that, so it will not be needed afterwards
cppast::cpp_entity_index idx;
// the parser is used to parse the entity
// there can be multiple parser implementations
cppast::libclang_parser parser(type_safe::ref(logger));
// parse the file
auto file = parser.parse(idx, filename, config);
if (fatal_error && parser.error())
return nullptr;
return file;
}
int main(int argc, char* argv[])
try
{
cxxopts::Options option_list("cppast",
"cppast - The commandline interface to the cppast library.\n");
// clang-format off
option_list.add_options()
("h,help", "display this help and exit")
("version", "display version information and exit")
("v,verbose", "be verbose when parsing")
("fatal_errors", "abort program when a parser error occurs, instead of doing error correction")
("file", "the file that is being parsed (last positional argument)",
cxxopts::value<std::string>());
option_list.add_options("compilation")
("database_dir", "set the directory where a 'compile_commands.json' file is located containing build information",
cxxopts::value<std::string>())
("database_file", "set the file name whose configuration will be used regardless of the current file name",
cxxopts::value<std::string>())
("std", "set the C++ standard (c++98, c++03, c++11, c++14, c++1z (experimental), c++17, c++2a, c++20)",
cxxopts::value<std::string>()->default_value(cppast::to_string(cppast::cpp_standard::cpp_latest)))
("I,include_directory", "add directory to include search path",
cxxopts::value<std::vector<std::string>>())
("D,macro_definition", "define a macro on the command line",
cxxopts::value<std::vector<std::string>>())
("U,macro_undefinition", "undefine a macro on the command line",
cxxopts::value<std::vector<std::string>>())
("f,feature", "enable a custom feature (-fXX flag)",
cxxopts::value<std::vector<std::string>>())
("gnu_extensions", "enable GNU extensions (equivalent to -std=gnu++XX)")
("msvc_extensions", "enable MSVC extensions (equivalent to -fms-extensions)")
("msvc_compatibility", "enable MSVC compatibility (equivalent to -fms-compatibility)")
("fast_preprocessing", "enable fast preprocessing, be careful, this breaks if you e.g. redefine macros in the same file!")
("remove_comments_in_macro", "whether or not comments generated by macro are kept, enable if you run into errors");
// clang-format on
option_list.parse_positional("file");
auto options = option_list.parse(argc, argv);
if (options.count("help"))
print_help(option_list);
else if (options.count("version"))
{
std::cout << "cppast version " << CPPAST_VERSION_STRING << "\n";
std::cout << "Copyright (C) Jonathan Müller 2017-2019 <[email protected]>\n";
std::cout << '\n';
std::cout << "Using libclang version " << CPPAST_CLANG_VERSION_STRING << '\n';
}
else if (!options.count("file") || options["file"].as<std::string>().empty())
{
print_error("missing file argument");
return 1;
}
else
{
// the compile config stores compilation flags
cppast::libclang_compile_config config;
if (options.count("database_dir"))
{
cppast::libclang_compilation_database database(
options["database_dir"].as<std::string>());
if (options.count("database_file"))
config
= cppast::libclang_compile_config(database,
options["database_file"].as<std::string>());
else
config
= cppast::libclang_compile_config(database, options["file"].as<std::string>());
}
if (options.count("verbose"))
config.write_preprocessed(true);
if (options.count("fast_preprocessing"))
config.fast_preprocessing(true);
if (options.count("remove_comments_in_macro"))
config.remove_comments_in_macro(true);
if (options.count("include_directory"))
for (auto& include : options["include_directory"].as<std::vector<std::string>>())
config.add_include_dir(include);
if (options.count("macro_definition"))
for (auto& macro : options["macro_definition"].as<std::vector<std::string>>())
{
auto equal = macro.find('=');
auto name = macro.substr(0, equal);
if (equal == std::string::npos)
config.define_macro(std::move(name), "");
else
{
auto def = macro.substr(equal + 1u);
config.define_macro(std::move(name), std::move(def));
}
}
if (options.count("macro_undefinition"))
for (auto& name : options["macro_undefinition"].as<std::vector<std::string>>())
config.undefine_macro(name);
if (options.count("feature"))
for (auto& name : options["feature"].as<std::vector<std::string>>())
config.enable_feature(name);
// the compile_flags are generic flags
cppast::compile_flags flags;
if (options.count("gnu_extensions"))
flags |= cppast::compile_flag::gnu_extensions;
if (options.count("msvc_extensions"))
flags |= cppast::compile_flag::ms_extensions;
if (options.count("msvc_compatibility"))
flags |= cppast::compile_flag::ms_compatibility;
if (options["std"].as<std::string>() == "c++98")
config.set_flags(cppast::cpp_standard::cpp_98, flags);
else if (options["std"].as<std::string>() == "c++03")
config.set_flags(cppast::cpp_standard::cpp_03, flags);
else if (options["std"].as<std::string>() == "c++11")
config.set_flags(cppast::cpp_standard::cpp_11, flags);
else if (options["std"].as<std::string>() == "c++14")
config.set_flags(cppast::cpp_standard::cpp_14, flags);
else if (options["std"].as<std::string>() == "c++1z")
config.set_flags(cppast::cpp_standard::cpp_1z, flags);
else if (options["std"].as<std::string>() == "c++17")
config.set_flags(cppast::cpp_standard::cpp_17, flags);
else if (options["std"].as<std::string>() == "c++2a")
config.set_flags(cppast::cpp_standard::cpp_2a, flags);
else if (options["std"].as<std::string>() == "c++20")
config.set_flags(cppast::cpp_standard::cpp_20, flags);
else
{
print_error("invalid value '" + options["std"].as<std::string>() + "' for std flag");
return 1;
}
// the logger is used to print diagnostics
cppast::stderr_diagnostic_logger logger;
if (options.count("verbose"))
logger.set_verbose(true);
auto file = parse_file(config, logger, options["file"].as<std::string>(),
options.count("fatal_errors") == 1);
if (!file)
return 2;
print_ast(std::cout, *file);
}
}
catch (const cppast::libclang_error& ex)
{
print_error(std::string("[fatal parsing error] ") + ex.what());
return 2;
}