• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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