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