Skip to main content

parse_file/
parse_file.rs

1//! Parse an LLVM IR `.ll` file from disk and print the round-tripped module.
2//!
3//! Companion to [`lex_file`](./lex_file.rs); the same single-I/O-round-trip
4//! pattern (read into `Vec<u8>`, hand the slice to the parser) but exercising
5//! the full module-level grammar instead of stopping at the token stream.
6//!
7//! Usage:
8//!     cargo run -p llvmkit-asmparser --example parse_file -- path/to/file.ll
9//!
10//! On success: prints the parsed [`llvmkit_ir::Module`] via its `Display`
11//! impl (the AsmWriter), giving a textual round-trip of the input. On a
12//! lex/parse error, prints a one-line `(line:col)` diagnostic against a
13//! [`SourceMap`] over the original bytes and exits with status 1.
14
15use std::fs::File;
16use std::path::{Path, PathBuf};
17use std::process::ExitCode;
18
19use llvmkit_asmparser::ll_parser::Parser;
20use llvmkit_asmparser::parse_error::ParseError;
21use llvmkit_asmparser::read_to_owned;
22use llvmkit_ir::Module;
23use llvmkit_support::{SourceMap, Span};
24
25fn main() -> ExitCode {
26    let Some(path) = std::env::args_os().nth(1).map(PathBuf::from) else {
27        eprintln!("usage: parse_file <path/to/file.ll>");
28        return ExitCode::from(2);
29    };
30
31    // Single I/O round-trip — the parser borrows from this slice for its
32    // entire run.
33    let bytes = match File::open(&path).and_then(read_to_owned) {
34        Ok(b) => b,
35        Err(e) => {
36            eprintln!("error: cannot read {}: {e}", path.display());
37            return ExitCode::from(1);
38        }
39    };
40
41    // A generative module brand keeps parser state from leaking past this
42    // closure (mirrors upstream `LLVMContext` ownership).
43    let module_name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("input");
44    Module::with_new::<_, _, _>(module_name, |module| {
45        let parser = match Parser::new(&bytes, &module) {
46            Ok(p) => p,
47            Err(err) => {
48                report_error(&path, &bytes, &err);
49                return ExitCode::from(1);
50            }
51        };
52
53        match parser.parse_module() {
54            Ok(_parsed) => {
55                // `_parsed.slot_mapping` carries the numbered-global table for
56                // follow-on `parse_constant_value` / `parse_type` calls; this
57                // example just round-trips the module.
58                print!("{module}");
59                ExitCode::SUCCESS
60            }
61            Err(err) => {
62                report_error(&path, &bytes, &err);
63                ExitCode::from(1)
64            }
65        }
66    })
67}
68
69fn report_error(path: &Path, src: &[u8], err: &ParseError) {
70    let sm = SourceMap::new(src);
71    let span = err.loc().map(|l| l.span).unwrap_or(Span::new(0, 0));
72    let (line, col) = sm.line_col(span.start);
73    eprintln!("{path}:{line}:{col}: {err}", path = path.display());
74    if let Some(line_bytes) = sm.line_text(line) {
75        eprintln!("  | {}", String::from_utf8_lossy(line_bytes));
76        let underline_len = (span.end.saturating_sub(span.start) as usize).max(1);
77        eprintln!(
78            "  | {pad}{caret}",
79            pad = " ".repeat((col - 1) as usize),
80            caret = "^".repeat(underline_len)
81        );
82    }
83}