1#![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)]
55struct Opts {
57 #[argh(option, default = "\"text\".to_owned()")]
58 format: String,
60
61 #[argh(positional)]
62 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 let start = usize::from(pos.start());
118 let end = usize::from(pos.end());
119
120 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
146fn main() -> Result<(), std::io::Error> {
148 let args: Opts = argh::from_env();
149
150 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 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}