Skip to content

Commit d81a36c

Browse files
committed
Add source/cpp2decl.cpp
1 parent 1764f0c commit d81a36c

File tree

1 file changed

+321
-0
lines changed

1 file changed

+321
-0
lines changed

source/cpp2decl.cpp

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// Copyright (c) Herb Sutter
2+
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3+
4+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+
// THE SOFTWARE.
11+
12+
// ----
13+
14+
// cpp2decl prints the declarations in a cpp2 file. For example, running on
15+
// regression-tests/pure2-types-inheritance.cpp2 (at commit 1764f0c) prints:
16+
//
17+
// Human : type
18+
// Human::speak : (this)
19+
// Human::operator= : (move this)
20+
// N::Machine : type
21+
// N::Machine::operator= : (out this, _: std::string)
22+
// N::Machine::work : (this)
23+
// N::Machine::operator= : (move this)
24+
// Cyborg : type
25+
// Cyborg::name : std::string
26+
// Cyborg::this : Human
27+
// Cyborg::address : std::string
28+
// Cyborg::this : N::Machine<(expression)>
29+
// Cyborg::operator= : (out this, n: std::string)
30+
// Cyborg::speak : (this)
31+
// Cyborg::work : (this)
32+
// Cyborg::print : (this)
33+
// Cyborg::operator= : (move this)
34+
// make_speak : (h: Human)
35+
// do_work : (m: N::Machine<(expression)>)
36+
// main : ()
37+
//
38+
// Declarations are printed one per line and their names are flattened, so that
39+
// the line for the `work` method on the `Machine` type in the `N` namespace
40+
// starts with `N::Machine::work`. This flattening is similar to how gron
41+
// (https://github.com/tomnomnom/gron) flattens JSON and has the same
42+
// motivation. Flattening combines well with classic, line-oriented, Unix-style
43+
// tools like diff, grep, sed and sort. Unlike the original C++2 source file,
44+
// which may have "N: namespace" and "Machine: type" on different lines,
45+
// sorting the lines of cpp2decl's output will not change its meaning.
46+
//
47+
// Many IDEs already have a "table of contents" view, but this command does not
48+
// require an IDE to provide a GUI or further context. For example, automated
49+
// tooling can combine cpp2decl, sort and diff to derive "what API changed
50+
// between two versions", similar to https://go.dev/api/go1.19.txt
51+
//
52+
// cpp2decl can also be used to get a quick overview of an unfamiliar corpus of
53+
// C++2 source code, and cpp2decl+grep can quickly answer ad hoc queries like
54+
// "show me all of the 'speak' methods implemented by cpp2 files in this
55+
// directory", without having to first import that project into an IDE or to
56+
// build (and keep up-to-date, e.g. when checking out different branches) a
57+
// code-search index.
58+
59+
// ----
60+
61+
// Build instructions: build cpp2decl.cpp just like building cppfront.cpp, per
62+
// the top-level README.md.
63+
64+
// ----
65+
66+
// TODO: a flag for writing to a file (a .cpp2decl file??) instead of always to
67+
// stdout. That would probably best start by refactoring cppfront.cpp.
68+
//
69+
// TODO: a flag for hiding private or anonymous-namespace names?
70+
//
71+
// TODO: a flag for hiding argument names (or replacing them with `_`)? Going
72+
// from `f(x: int)` to `f(y: int)` isn't actually an API change.
73+
//
74+
// TODO: fix the `(expression)` in `N::Machine<(expression)>`.
75+
//
76+
// TODO: also parse C++1 syntax, which is obviously easier said than done.
77+
//
78+
// TODO: paint the bikeshed about cpp2decl's output syntax.
79+
80+
// ----
81+
82+
// This file was formatted by clang-format (using its default options).
83+
84+
// ----
85+
86+
#include <cstdio>
87+
#include <iostream>
88+
#include <optional>
89+
#include <sstream>
90+
91+
// TODO: is there a better header? We don't actually need semantic analysis.
92+
#include "sema.h"
93+
94+
namespace cpp2 {
95+
96+
namespace {
97+
98+
auto do_declaration_name(std::ostream& out, declaration_node const& n)
99+
-> size_t {
100+
size_t num_printed = 0;
101+
if (n.parent_declaration) {
102+
num_printed = 2 + do_declaration_name(out, *n.parent_declaration);
103+
out << "::";
104+
}
105+
std::string_view sv = std::string_view(*n.identifier->identifier);
106+
out << sv;
107+
return num_printed + sv.size();
108+
};
109+
110+
auto do_parameters(std::ostream& out,
111+
parameter_declaration_list_node& n,
112+
passing_style default_passing_style) -> void {
113+
out << "(";
114+
bool first = true;
115+
for (std::unique_ptr<parameter_declaration_node> const& pdn : n.parameters) {
116+
if (!first) {
117+
out << ", ";
118+
}
119+
std::string_view const& name = *pdn->name();
120+
// TODO: print modifiers like "override".
121+
if (passing_style ps = pdn->direction(); ps != default_passing_style) {
122+
out << to_string_view(ps) << " ";
123+
}
124+
out << name;
125+
if (name != "this") {
126+
out << ": ";
127+
if (pdn->declaration->is_object()) {
128+
auto const& o =
129+
std::get<declaration_node::an_object>(pdn->declaration->type);
130+
for (auto const& q : o->pc_qualifiers) {
131+
out << *q << " ";
132+
}
133+
out << pdn->declaration->object_type();
134+
} else {
135+
out << "???";
136+
}
137+
}
138+
first = false;
139+
}
140+
out << ")";
141+
}
142+
143+
auto do_function(std::ostream& out, function_type_node& n) -> void {
144+
do_parameters(out, *n.parameters, passing_style::in);
145+
146+
switch (n.returns.index()) {
147+
case function_type_node::empty: {
148+
break;
149+
}
150+
case function_type_node::id: {
151+
function_type_node::single_type_id const& sti =
152+
std::get<function_type_node::id>(n.returns);
153+
out << " -> ";
154+
if (passing_style ps = sti.pass; ps != passing_style::move) {
155+
out << to_string_view(ps) << " ";
156+
}
157+
out << sti.type->to_string();
158+
break;
159+
}
160+
case function_type_node::list: {
161+
std::unique_ptr<parameter_declaration_list_node>& pdln =
162+
std::get<function_type_node::list>(n.returns);
163+
out << " -> ";
164+
do_parameters(out, *pdln, passing_style::out);
165+
break;
166+
}
167+
}
168+
}
169+
170+
auto do_declaration(std::ostream& out, declaration_node const& n) -> void {
171+
if (!n.is_namespace()) {
172+
size_t num_printed = do_declaration_name(out, n);
173+
if (num_printed < 32) {
174+
static const char* thirty_two_spaces = " ";
175+
out << thirty_two_spaces + num_printed << ": ";
176+
} else {
177+
out << " : ";
178+
}
179+
180+
switch (n.type.index()) {
181+
case declaration_node::a_function: {
182+
do_function(out, *std::get<declaration_node::a_function>(n.type));
183+
break;
184+
}
185+
case declaration_node::an_object: {
186+
auto const& o = std::get<declaration_node::an_object>(n.type);
187+
for (auto const& q : o->pc_qualifiers) {
188+
out << *q << " ";
189+
}
190+
out << n.object_type();
191+
break;
192+
}
193+
case declaration_node::a_type: {
194+
// TODO: print "@interface", "@polymorphic_base", "<I:int>", etc.
195+
out << "type";
196+
break;
197+
}
198+
}
199+
out << "\n";
200+
}
201+
202+
if (n.is_namespace() || n.is_type()) {
203+
auto& c = std::get<statement_node::compound>(n.initializer->statement);
204+
for (auto& s : c->statements) {
205+
if (s->is_declaration()) {
206+
auto& d = std::get<statement_node::declaration>(s->statement);
207+
do_declaration(out, *d);
208+
}
209+
}
210+
}
211+
}
212+
213+
} // namespace
214+
215+
auto cmdline_processor::print(std::string_view s, int width) -> void {
216+
if (width > 0) {
217+
std::cout << std::setw(width) << std::left;
218+
}
219+
std::cout << s;
220+
}
221+
222+
auto error_entry::print(auto& o, std::string const& file) const -> void {
223+
o << file;
224+
if (where.lineno > 0) {
225+
o << "(" << (where.lineno);
226+
if (where.colno >= 0) {
227+
o << "," << where.colno;
228+
}
229+
o << ")";
230+
}
231+
o << ":";
232+
if (internal) {
233+
o << " internal compiler";
234+
}
235+
o << " error: " << msg << "\n";
236+
}
237+
238+
class cpp2decl {
239+
std::string sourcefile;
240+
std::vector<error_entry> errors;
241+
242+
cpp2::source source;
243+
cpp2::tokens tokens;
244+
cpp2::parser parser;
245+
246+
public:
247+
cpp2decl(std::string const& filename)
248+
: sourcefile{filename}, source{errors}, tokens{errors}, parser{errors} {
249+
if (!sourcefile.ends_with(".cpp2") && !sourcefile.ends_with(".h2")) {
250+
errors.emplace_back(
251+
source_position(-1, -1),
252+
"source filename must end with .cpp2 or .h2: " + sourcefile);
253+
} else if (!source.load(sourcefile)) {
254+
if (errors.empty()) {
255+
errors.emplace_back(source_position(-1, -1),
256+
"file not found: " + sourcefile);
257+
}
258+
} else {
259+
tokens.lex(source.get_lines());
260+
try {
261+
for (auto const& [line, entry] : tokens.get_map()) {
262+
if (!parser.parse(entry, tokens.get_generated())) {
263+
errors.emplace_back(source_position(line, 0),
264+
"parse failed for section starting here", false,
265+
true);
266+
}
267+
}
268+
} catch (std::runtime_error& e) {
269+
errors.emplace_back(source_position(-1, -1), e.what());
270+
}
271+
}
272+
}
273+
274+
auto run(std::ostream& out) -> void {
275+
for (auto& section : tokens.get_map()) {
276+
auto decls = parser.get_parse_tree_declarations_in_range(section.second);
277+
for (auto const& decl : decls) {
278+
do_declaration(out, *decl);
279+
}
280+
}
281+
}
282+
283+
auto print_errors(std::ostream& out) -> void {
284+
error_entry const* prev = {};
285+
bool print_fallback_errors = true;
286+
for (auto&& error : errors) {
287+
if (!error.fallback) {
288+
print_fallback_errors = false;
289+
}
290+
if (error.fallback && !print_fallback_errors) {
291+
continue;
292+
}
293+
if (!prev || error != *prev) {
294+
error.print(out, strip_path(sourcefile));
295+
}
296+
prev = &error;
297+
}
298+
}
299+
300+
auto had_no_errors() -> bool { return errors.empty(); }
301+
};
302+
303+
} // namespace cpp2
304+
305+
auto main(int argc, char* argv[]) -> int {
306+
cpp2::cmdline.set_args(argc, argv);
307+
cpp2::cmdline.process_flags();
308+
std::stringbuf str_buf;
309+
std::ostream os(&str_buf);
310+
int exit_status = EXIT_SUCCESS;
311+
for (auto const& arg : cpp2::cmdline.arguments()) {
312+
cpp2::cpp2decl c(arg.text);
313+
c.run(os);
314+
if (!c.had_no_errors()) {
315+
c.print_errors(std::cerr);
316+
exit_status = EXIT_FAILURE;
317+
}
318+
}
319+
std::cout << str_buf.str();
320+
return exit_status;
321+
}

0 commit comments

Comments
 (0)