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
/*[toml]
[dependencies]
# Alternatively can use `use proc_macro2;` with dependency inference and default config
proc-macro2 = { version = "1", features = ["span-locations"] }
thag_profiler = { version = "0.1, thag-auto", features = ["full_profiling", "debug_logging"] }
*/
/// A version of the published example from the `syn` crate used to demonstrate profiling a dependency with `thag_profiler`.
/// Description "Parse a Rust source file into a `syn::File` and print out a debug representation of the syntax tree."
///
/// Pass it the absolute or relative path of any Rust PROGRAM source file, e.g. its own
/// path that you passed to the script runner to invoke it.
///
/// NB: Pick a script that is a valid program (containing `fn main()` as opposed to a snippet).
///
/// E.g.:
///
/// ```
/// THAG_PROFILER=both,,announce,true thag demo/syn_dump_syntax_profile_syn.rs -tf -- demo/hello_main.rs
/// ```
///
/// See the `README.md` for the explanation of the `THAG_PROFILER` arguments
//# Purpose: demonstrate profiling a dependency with `thag_profiler`.
//# Categories: AST, crates, technique
//# Sample arguments: `-- demo/hello_main.rs`
// Parse a Rust source file into a `syn::File` and print out a debug
// representation of the syntax tree.
//
// Use the following command from this directory to test this program by
// running it on its own source code:
//
// cargo run -- src/main.rs
//
// The output will begin with:
//
// File {
// shebang: None,
// attrs: [
// Attribute {
// pound_token: Pound,
// style: AttrStyle::Inner(
// ...
// }
use colored::Colorize;
use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::fmt::{self, Display};
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use thag_profiler::{self, enable_profiling};
enum Error {
IncorrectUsage,
ReadFile(io::Error),
ParseFile {
error: syn::Error,
filepath: PathBuf,
source_code: String,
},
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"),
Error::ReadFile(error) => write!(f, "Unable to read file: {}", error),
Error::ParseFile {
error,
filepath,
source_code,
} => render_location(f, error, filepath, source_code),
}
}
}
#[enable_profiling(runtime)]
fn main() {
eprintln!(
"is_profiling_enabled()? {}, get_global_profile_type(): {:?}",
thag_profiler::is_profiling_enabled(),
thag_profiler::get_global_profile_type()
);
if let Err(error) = try_main() {
let _ = writeln!(io::stderr(), "{}", error);
process::exit(1);
}
}
fn try_main() -> Result<(), Error> {
let mut args = env::args_os();
let _ = args.next(); // executable name
let filepath = match (args.next(), args.next()) {
(Some(arg), None) => PathBuf::from(arg),
_ => return Err(Error::IncorrectUsage),
};
let code = fs::read_to_string(&filepath).map_err(Error::ReadFile)?;
let syntax = syn::parse_file(&code).map_err({
|error| Error::ParseFile {
error,
filepath,
source_code: code,
}
})?;
println!("{:#?}", syntax);
Ok(())
}
// Render a rustc-style error message, including colors.
//
// error: Syn unable to parse file
// --> main.rs:40:17
// |
// 40 | fn fmt(&self formatter: &mut fmt::Formatter) -> fmt::Result {
// | ^^^^^^^^^ expected `,`
//
fn render_location(
formatter: &mut fmt::Formatter,
err: &syn::Error,
filepath: &Path,
code: &str,
) -> fmt::Result {
let start = err.span().start();
let mut end = err.span().end();
let code_line = match start.line.checked_sub(1).and_then(|n| code.lines().nth(n)) {
Some(line) => line,
None => return render_fallback(formatter, err),
};
if end.line > start.line {
end.line = start.line;
end.column = code_line.len();
}
let filename = filepath
.file_name()
.map(OsStr::to_string_lossy)
.unwrap_or(Cow::Borrowed("main.rs"));
write!(
formatter,
"\n\
{error}{header}\n\
{indent}{arrow} {filename}:{linenum}:{colnum}\n\
{indent} {pipe}\n\
{label} {pipe} {code}\n\
{indent} {pipe} {offset}{underline} {message}\n\
",
error = "error".red().bold(),
header = ": Syn unable to parse file".bold(),
indent = " ".repeat(start.line.to_string().len()),
arrow = "-->".blue().bold(),
filename = filename,
linenum = start.line,
colnum = start.column,
pipe = "|".blue().bold(),
label = start.line.to_string().blue().bold(),
code = code_line.trim_end(),
offset = " ".repeat(start.column),
underline = "^"
.repeat(end.column.saturating_sub(start.column).max(1))
.red()
.bold(),
message = err.to_string().red(),
)
}
fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result {
write!(formatter, "Unable to parse file: {}", err)
}