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 20use colored::Colorize; 21use std::borrow::Cow; 22use std::env; 23use std::ffi::OsStr; 24use std::fmt::{self, Display}; 25use std::fs; 26use std::io::{self, Write}; 27use std::path::{Path, PathBuf}; 28use std::process; 29 30enum Error { 31 IncorrectUsage, 32 ReadFile(io::Error), 33 ParseFile { 34 error: syn::Error, 35 filepath: PathBuf, 36 source_code: String, 37 }, 38} 39 40impl Display for Error { 41 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 56fn main() { 57 if let Err(error) = try_main() { 58 let _ = writeln!(io::stderr(), "{}", error); 59 process::exit(1); 60 } 61} 62 63fn 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// 93fn 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 145fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result { 146 write!(formatter, "Unable to parse file: {}", err) 147} 148