glsl_lang_cli/
main.rs

1//! [`glsl-lang`](https://crates.io/crates/glsl-lang) debugging CLI.
2//!
3//! *This is only a prototype for debugging, more options will be added in later updates.*
4//!
5//! # Usage
6//!
7//! Print GLSL AST to the standard output:
8//! ```bash
9//! $ cargo run < source.glsl
10//! TranslationUnit
11//!   ExternalDeclaration@0:0..45 `Declaration`
12//!     Declaration@0:0..45 `Block`
13//!       [...]
14//! ```
15
16#![deny(missing_docs)]
17
18use std::{io::prelude::*, path::Path};
19
20use argh::FromArgs;
21
22use glsl_lang::{
23    ast::{NodeDisplay, TranslationUnit},
24    lexer::full::fs::PreprocessorExt,
25    parse::IntoParseBuilderExt,
26};
27
28fn output_text(output: &mut dyn std::io::Write, tu: TranslationUnit) -> std::io::Result<()> {
29    writeln!(output, "{}", tu.display())?;
30    Ok(())
31}
32
33#[cfg(feature = "json")]
34fn output_json(output: &mut dyn std::io::Write, tu: TranslationUnit) -> std::io::Result<()> {
35    serde_json::to_writer(output, &tu)?;
36    Ok(())
37}
38
39fn output_glsl(output: &mut dyn std::io::Write, tu: TranslationUnit) -> std::io::Result<()> {
40    let mut s = String::new();
41
42    glsl_lang::transpiler::glsl::show_translation_unit(
43        &mut s,
44        &tu,
45        glsl_lang::transpiler::glsl::FormattingState::default(),
46    )
47    .unwrap();
48
49    write!(output, "{}", s)?;
50
51    Ok(())
52}
53
54#[derive(Debug, FromArgs)]
55/// glsl-lang command-line interface
56struct Opts {
57    #[argh(option, default = "\"text\".to_owned()")]
58    /// output format (text, json or glsl)
59    format: String,
60
61    #[argh(positional)]
62    /// input file path
63    path: Option<String>,
64}
65
66use miette::{Diagnostic, SourceSpan};
67
68#[derive(Debug, Diagnostic)]
69#[diagnostic(code(glsl_lang::parse::error))]
70struct ParseError<I: std::error::Error + 'static> {
71    inner: lang_util::located::Located<I>,
72    #[source_code]
73    src: NamedSource<String>,
74    snip: SourceSpan,
75    #[label = "Error occurred here."]
76    bad_bit: SourceSpan,
77}
78
79impl<I: std::error::Error + 'static> std::error::Error for ParseError<I> {
80    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81        Some(self.inner.inner())
82    }
83}
84
85impl<I: std::error::Error> std::fmt::Display for ParseError<I> {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(
88            f,
89            "Failed to parse input GLSL at line {} column {}.",
90            self.inner.line() + 1,
91            self.inner.col() + 1
92        )
93    }
94}
95
96use miette::{NamedSource, Result};
97fn parse_tu(source: &str, path: &str) -> Result<glsl_lang::ast::TranslationUnit> {
98    let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
99    let tu: Result<glsl_lang::ast::TranslationUnit, _> = processor
100        .open_source(
101            source,
102            Path::new(path).parent().unwrap_or_else(|| Path::new(".")),
103        )
104        .builder()
105        .parse()
106        .map(|(mut tu, _, iter)| {
107            iter.into_directives().inject(&mut tu);
108            tu
109        });
110
111    match tu {
112        Ok(tu) => Ok(tu),
113        Err(err) => {
114            let pos = err.pos();
115
116            // Find 2 lines before and after
117            let start = usize::from(pos.start());
118            let end = usize::from(pos.end());
119
120            // TODO: '\n' isn't what GLSL calls a line
121            let before = source
122                .rmatch_indices('\n')
123                .filter(|(i, _ch)| *i < start)
124                .map(|(i, _ch)| i + 1)
125                .nth(2)
126                .unwrap_or(0);
127
128            let after = source
129                .match_indices('\n')
130                .filter(|(i, _ch)| *i > end)
131                .map(|(i, _ch)| i)
132                .nth(2)
133                .unwrap_or(source.len());
134
135            Err(ParseError {
136                inner: err,
137                src: NamedSource::new(path, source.to_string()),
138                snip: (before, after.saturating_sub(before)).into(),
139                bad_bit: (usize::from(pos.start()), usize::from(pos.len())).into(),
140            }
141            .into())
142        }
143    }
144}
145
146/// CLI entry point
147fn main() -> Result<(), std::io::Error> {
148    let args: Opts = argh::from_env();
149
150    // Figure out output format
151    let output_fn = match args.format.as_str() {
152        "text" => output_text,
153        #[cfg(feature = "json")]
154        "json" => output_json,
155        "glsl" => output_glsl,
156        other => panic!("unknown output format: {}", other),
157    };
158
159    let mut s = String::new();
160
161    // Read input from argument or stdin
162    if let Some(path) = args.path.as_deref() {
163        s = std::fs::read_to_string(path)?;
164    } else {
165        std::io::stdin().read_to_string(&mut s)?;
166    }
167
168    match parse_tu(
169        s.as_str(),
170        &args
171            .path
172            .as_ref()
173            .map(String::to_owned)
174            .unwrap_or_else(|| "standard input".to_owned()),
175    ) {
176        Ok(tu) => {
177            output_fn(&mut std::io::stdout(), tu)?;
178        }
179        Err(diag) => {
180            eprintln!("{:?}", diag);
181            std::process::exit(1);
182        }
183    }
184
185    Ok(())
186}