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: 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 _ = 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 _ = 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 if start.line == end.line && start.column == end.column {
103 return render_fallback(formatter, err);
104 }
105
106 let code_line = match code.lines().nth(start.line - 1) {
107 Some(line) => line,
108 None => return render_fallback(formatter, err),
109 };
110
111 if end.line > start.line {
112 end.line = start.line;
113 end.column = code_line.len();
114 }
115
116 let filename = filepath
117 .file_name()
118 .map(OsStr::to_string_lossy)
119 .unwrap_or(Cow::Borrowed("main.rs"));
120
121 write!(
122 formatter,
123 "\n\
124 {error}{header}\n\
125 {indent}{arrow} {filename}:{linenum}:{colnum}\n\
126 {indent} {pipe}\n\
127 {label} {pipe} {code}\n\
128 {indent} {pipe} {offset}{underline} {message}\n\
129 ",
130 error = "error".red().bold(),
131 header = ": Syn unable to parse file".bold(),
132 indent = " ".repeat(start.line.to_string().len()),
133 arrow = "-->".blue().bold(),
134 filename = filename,
135 linenum = start.line,
136 colnum = start.column,
137 pipe = "|".blue().bold(),
138 label = start.line.to_string().blue().bold(),
139 code = code_line.trim_end(),
140 offset = " ".repeat(start.column),
141 underline = "^".repeat(end.column - start.column).red().bold(),
142 message = err.to_string().red(),
143 )
144 }
145
render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result146 fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result {
147 write!(formatter, "Unable to parse file: {}", err)
148 }
149