1 //! Parse a Rust source file into a `syn::File` and print out a debug
2 //! representation of the syntax tree.
3 //!
4 //! Use the following command from this directory to test this program by
5 //! running it on its own source code:
6 //!
7 //! cargo run -- src/main.rs
8 //!
9 //! The output will begin with:
10 //!
11 //! File {
12 //! shebang: None,
13 //! attrs: [
14 //! Attribute {
15 //! pound_token: Pound,
16 //! style: AttrStyle::Inner(
17 //! ...
18 //! }
19
20 use colored::Colorize;
21 use std::borrow::Cow;
22 use std::env;
23 use std::ffi::OsStr;
24 use std::fmt::{self, Display};
25 use std::fs;
26 use std::io::{self, Write};
27 use std::path::{Path, PathBuf};
28 use std::process;
29
30 enum Error {
31 IncorrectUsage,
32 ReadFile(io::Error),
33 ParseFile {
34 error: syn::Error,
35 filepath: PathBuf,
36 source_code: String,
37 },
38 }
39
40 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 use self::Error::*;
43
44 match self {
45 IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"),
46 ReadFile(error) => write!(f, "Unable to read file: {}", error),
47 ParseFile {
48 error,
49 filepath,
50 source_code,
51 } => render_location(f, error, filepath, source_code),
52 }
53 }
54 }
55
main()56 fn main() {
57 if let Err(error) = try_main() {
58 let _ = writeln!(io::stderr(), "{}", error);
59 process::exit(1);
60 }
61 }
62
try_main() -> Result<(), Error>63 fn try_main() -> Result<(), Error> {
64 let mut args = env::args_os();
65 let _ = args.next(); // executable name
66
67 let filepath = match (args.next(), args.next()) {
68 (Some(arg), None) => PathBuf::from(arg),
69 _ => return Err(Error::IncorrectUsage),
70 };
71
72 let code = fs::read_to_string(&filepath).map_err(Error::ReadFile)?;
73 let syntax = syn::parse_file(&code).map_err({
74 |error| Error::ParseFile {
75 error,
76 filepath,
77 source_code: code,
78 }
79 })?;
80 println!("{:#?}", syntax);
81
82 Ok(())
83 }
84
85 // Render a rustc-style error message, including colors.
86 //
87 // error: Syn unable to parse file
88 // --> main.rs:40:17
89 // |
90 // 40 | fn fmt(&self formatter: &mut fmt::Formatter) -> fmt::Result {
91 // | ^^^^^^^^^ expected `,`
92 //
render_location( formatter: &mut fmt::Formatter, err: &syn::Error, filepath: &Path, code: &str, ) -> fmt::Result93 fn render_location(
94 formatter: &mut fmt::Formatter,
95 err: &syn::Error,
96 filepath: &Path,
97 code: &str,
98 ) -> fmt::Result {
99 let start = err.span().start();
100 let mut end = err.span().end();
101
102 let code_line = match start.line.checked_sub(1).and_then(|n| code.lines().nth(n)) {
103 Some(line) => line,
104 None => return render_fallback(formatter, err),
105 };
106
107 if end.line > start.line {
108 end.line = start.line;
109 end.column = code_line.len();
110 }
111
112 let filename = filepath
113 .file_name()
114 .map(OsStr::to_string_lossy)
115 .unwrap_or(Cow::Borrowed("main.rs"));
116
117 write!(
118 formatter,
119 "\n\
120 {error}{header}\n\
121 {indent}{arrow} {filename}:{linenum}:{colnum}\n\
122 {indent} {pipe}\n\
123 {label} {pipe} {code}\n\
124 {indent} {pipe} {offset}{underline} {message}\n\
125 ",
126 error = "error".red().bold(),
127 header = ": Syn unable to parse file".bold(),
128 indent = " ".repeat(start.line.to_string().len()),
129 arrow = "-->".blue().bold(),
130 filename = filename,
131 linenum = start.line,
132 colnum = start.column,
133 pipe = "|".blue().bold(),
134 label = start.line.to_string().blue().bold(),
135 code = code_line.trim_end(),
136 offset = " ".repeat(start.column),
137 underline = "^"
138 .repeat(end.column.saturating_sub(start.column).max(1))
139 .red()
140 .bold(),
141 message = err.to_string().red(),
142 )
143 }
144
render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result145 fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result {
146 write!(formatter, "Unable to parse file: {}", err)
147 }
148