Skip to content

Commit 405e49f

Browse files
committed
Merge branch 'vjp/diagnostics' into lsp-main
2 parents e579c05 + 1d419bc commit 405e49f

File tree

6 files changed

+304
-14
lines changed

6 files changed

+304
-14
lines changed

source/common.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,7 @@ struct error_entry
989989
{
990990
source_position where;
991991
std::string msg;
992+
std::string symbol;
992993
bool internal = false;
993994
bool fallback = false; // only emit this message if there was nothing better
994995

@@ -1004,6 +1005,20 @@ struct error_entry
10041005
, fallback{f}
10051006
{ }
10061007

1008+
error_entry(
1009+
source_position w,
1010+
std::string_view m,
1011+
std::string_view s,
1012+
bool i = false,
1013+
bool f = false
1014+
)
1015+
: where{w}
1016+
, msg{m}
1017+
, symbol{s}
1018+
, internal{i}
1019+
, fallback{f}
1020+
{ }
1021+
10071022
auto operator==(error_entry const& that)
10081023
-> bool
10091024
{

source/cppfront.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ static cpp2::cmdline_processor::register_flag cmd_debug(
2020
[]{ flag_debug_output = true; }
2121
);
2222

23+
static auto flag_diagnostics = std::string();
24+
static cpp2::cmdline_processor::register_flag cmd_diagnostics(
25+
9,
26+
"diagnostics outfile",
27+
"Emit compiler diagnostics output to outfile (or diagnostics.json if not specified)",
28+
nullptr,
29+
[](std::string const& output){ flag_diagnostics = output; }
30+
);
31+
2332
static auto flag_quiet = false;
2433
static cpp2::cmdline_processor::register_flag cmd_quiet(
2534
9,
@@ -157,10 +166,15 @@ auto main(
157166
exit_status = EXIT_FAILURE;
158167
}
159168

160-
// And, if requested, the debug information
169+
// if requested, the debug information
161170
if (flag_debug_output) {
162171
c.debug_print();
163172
}
173+
174+
// And, if requested, the diagnostics information
175+
if (flag_diagnostics != "") {
176+
c.diagnostics_print(flag_diagnostics);
177+
}
164178
}
165179

166180
//if (flag_internal_debug) {

source/diagnostics.h

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
2+
// Copyright (c) Herb Sutter
3+
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
4+
5+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
8+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
10+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
11+
// THE SOFTWARE.
12+
13+
14+
//===========================================================================
15+
// Aggregate compiler diagnostics
16+
//===========================================================================
17+
18+
#ifndef DIAGNOSTICS_H
19+
#define DIAGNOSTICS_H
20+
21+
#include "sema.h"
22+
#include <filesystem>
23+
24+
namespace cpp2 {
25+
26+
//------------------//
27+
// Type definitions //
28+
//------------------//
29+
30+
/** A type that holds info about a declaration/symbol in the source */
31+
struct diagnostic_symbol_t {
32+
std::string symbol;
33+
std::string kind;
34+
std::string scope;
35+
cpp2::source_position position;
36+
37+
auto operator<=>(const diagnostic_symbol_t& other) const = default;
38+
};
39+
40+
/** A type denoting a range of text in the source */
41+
struct diagnostic_scope_range_t {
42+
cpp2::source_position start;
43+
cpp2::source_position end;
44+
};
45+
using diagnostic_scope_map = std::unordered_map<std::string, diagnostic_scope_range_t>;
46+
47+
/** The main diagnostics type used to communicate compiler results to external programs */
48+
struct diagnostics_t {
49+
std::set<diagnostic_symbol_t> symbols;
50+
std::vector<cpp2::error_entry> errors;
51+
diagnostic_scope_map scope_map;
52+
};
53+
54+
//------------------//
55+
// Helper Functions //
56+
//------------------//
57+
58+
/** Determine the kind of declaration we have */
59+
auto get_declaration_kind(const cpp2::declaration_node* decl ) -> std::string {
60+
if (decl->is_function()) return "function";
61+
if (decl->is_object()) return "var";
62+
if (decl->is_type()) return "type";
63+
if (decl->is_namespace()) return "namespace";
64+
return "unknown";
65+
}
66+
67+
/** Get the identifier/name of the declaration */
68+
auto get_decl_name(const cpp2::declaration_node* decl) -> std::string {
69+
if (decl == nullptr || decl->identifier == nullptr ) return std::string();
70+
71+
return decl->identifier->to_string();
72+
}
73+
74+
/**
75+
* Put a dot between strings if `scope` isn't empty
76+
* Used to create fully qualified scope names
77+
*/
78+
auto dot(std::string scope) {
79+
return scope.empty() ? "" : ".";
80+
}
81+
82+
/** Get all parent scopes of the declaration */
83+
auto get_decl_scope(const cpp2::declaration_node* decl) -> std::string {
84+
std::string result = {};
85+
auto parent = decl->get_parent();
86+
87+
while(parent != nullptr) {
88+
result = parent->identifier->to_string() + dot(result) + result;
89+
parent = parent->get_parent();
90+
}
91+
return result;
92+
}
93+
94+
/** Read a declaration_sym into a diagnostic_symbol_t */
95+
auto read_symbol(const cpp2::declaration_sym* sym) -> diagnostic_symbol_t {
96+
return diagnostic_symbol_t{
97+
sym->identifier->to_string(),
98+
get_declaration_kind(sym->declaration),
99+
get_decl_scope(sym->declaration),
100+
sym->declaration->position()
101+
};
102+
}
103+
104+
/** Gather together the scope ranges for all of our scope-owning declarations */
105+
auto make_scope_map(const cpp2::sema& sema) -> diagnostic_scope_map
106+
{
107+
diagnostic_scope_map result = {};
108+
auto current = std::string();
109+
110+
for(auto& s : sema.symbols)
111+
{
112+
switch (s.sym.index())
113+
{
114+
// If the symbol is a declaration with its own scope,
115+
// we save the name of in `current`.
116+
//
117+
break; case symbol::active::declaration:
118+
{
119+
auto const& sym = std::get<symbol::active::declaration>(s.sym);
120+
if (sym.declaration == nullptr) break;
121+
122+
if (sym.declaration->is_function() ||
123+
sym.declaration->is_namespace() ||
124+
sym.declaration->is_type() )
125+
{
126+
if( sym.identifier == nullptr ) break;
127+
// Grab all parent scopes here to use for the scope map entry
128+
auto scope = get_decl_scope(sym.declaration);
129+
current = scope + dot(scope) + sym.identifier->to_string();
130+
}
131+
}
132+
133+
// If the symbol is a scope symbol, we save the position of where
134+
// the scope opens and closes in our scope map at `current`.
135+
break; case symbol::active::compound:
136+
{
137+
auto const& sym = std::get<symbol::active::compound>(s.sym);
138+
if (sym.kind_ == sym.is_scope && sym.start) {
139+
auto c = sym.compound;
140+
result[current] = diagnostic_scope_range_t{c->open_brace, c->close_brace};
141+
}
142+
}
143+
144+
break; default: break;
145+
}
146+
}
147+
148+
return result;
149+
}
150+
151+
/** Sanitize a string to make sure its json parsable */
152+
auto sanitize_for_json(const std::string& s) -> std::string {
153+
std::string result = s;
154+
155+
// Replace special characters with their escaped versions
156+
cpp2::replace_all(result, "\\", "\\\\"); // Escape backslash
157+
cpp2::replace_all(result, "\"", "\\\""); // Escape double quotes
158+
cpp2::replace_all(result, "\b", "\\b"); // Escape backspace
159+
cpp2::replace_all(result, "\f", "\\f"); // Escape formfeed
160+
cpp2::replace_all(result, "\n", "\\n"); // Escape newlines
161+
cpp2::replace_all(result, "\r", "\\r"); // Escape carriage return
162+
cpp2::replace_all(result, "\t", "\\t"); // Escape tab
163+
164+
return result;
165+
}
166+
167+
//----------------//
168+
// Main Functions //
169+
//----------------//
170+
171+
/** Takes a `sema` and aggregates all the diagnostics info */
172+
auto get_diagnostics(const cpp2::sema& sema) -> diagnostics_t {
173+
std::set<diagnostic_symbol_t> symbols = {};
174+
diagnostic_scope_map scope_map = make_scope_map(sema);
175+
176+
// Gather together all of the identifier declarations, along with their position
177+
for (auto& d : sema.declaration_of) {
178+
symbols.emplace(read_symbol(d.second.sym));
179+
}
180+
181+
return diagnostics_t{symbols, sema.errors, scope_map};
182+
}
183+
184+
/** Prints the compiler diagnostics to an ostream (either stdout or file) */
185+
auto print_diagnostics(std::ostream &o, diagnostics_t diagnostics) -> void {
186+
187+
// Print out symbol info to json
188+
o << "{\"symbols\": [";
189+
for(auto i = 0; auto& d : diagnostics.symbols) {
190+
++i;
191+
o
192+
<< "{ \"symbol\": \"" << d.symbol << "\", "
193+
<< "\"scope\": \"" << d.scope << "\", "
194+
<< "\"kind\": \"" << d.kind << "\", "
195+
<< "\"lineno\": " << d.position.lineno << ", "
196+
<< "\"colno\": " << d.position.colno
197+
<< (i < diagnostics.symbols.size() ? "},\n" : "}\n");
198+
}
199+
200+
// Print out error entries to json
201+
o << "],\n \"errors\": [";
202+
for(auto i = 0; auto& e : diagnostics.errors) {
203+
++i;
204+
o
205+
<< "{\"symbol\": \"" << e.symbol << "\", "
206+
<< "\"lineno\": " << e.where.lineno << ", "
207+
<< "\"colno\": " << e.where.colno << ", "
208+
<< "\"msg\": \"" << sanitize_for_json(e.msg)
209+
<< (i < diagnostics.errors.size() ? "\"},\n" : "\"}\n");
210+
}
211+
212+
// Print out the our scope's source ranges as a map/object where:
213+
// keys - are the scope symbol names,
214+
// values - are their source range
215+
//
216+
o << "],\n \"scopes\": {";
217+
for(auto i = 0; auto& s : diagnostics.scope_map) {
218+
++i;
219+
o
220+
<< "\"" << s.first << "\":"
221+
<< "{\"start\": { \"lineno\": " << s.second.start.lineno << ", "
222+
<< "\"colno\": " << s.second.start.colno << "},"
223+
<< "\"end\": { \"lineno\": " << s.second.end.lineno << ", "
224+
<< "\"colno\": " << s.second.end.colno
225+
<< (i < diagnostics.scope_map.size() ? "}},\n" : "}}\n");
226+
}
227+
228+
o << "}}";
229+
}
230+
}
231+
232+
#endif

source/parse.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6064,15 +6064,16 @@ class parser
60646064
auto m = std::string{msg};
60656065
auto i = done() ? -1 : 0;
60666066
assert (peek(i));
6067+
auto s = peek(i)->to_string();
60676068
if (include_curr_token) {
6068-
m += std::string(" (at '") + peek(i)->to_string() + "')";
6069+
m += std::string(" (at '") + s + "')";
60696070
}
60706071
if (
60716072
err_pos == source_position{}
60726073
) {
60736074
err_pos = peek(i)->position();
60746075
}
6075-
errors.emplace_back( err_pos, m, false, fallback );
6076+
errors.emplace_back( err_pos, m, s, false, fallback );
60766077
}
60776078

60786079
auto error(

0 commit comments

Comments
 (0)