shader_sense/symbols/
prepocessor.rs

1//! Preprocessor API
2
3use std::{
4    collections::HashSet,
5    path::{Path, PathBuf},
6};
7
8use crate::{
9    include::IncludeHandler,
10    position::{ShaderFileRange, ShaderRange},
11    shader::ShaderContextParams,
12    shader_error::ShaderDiagnostic,
13    symbols::{
14        shader_module::ShaderSymbols,
15        symbol_list::{ShaderSymbolList, ShaderSymbolListRef},
16        symbols::{
17            ShaderSymbol, ShaderSymbolData, ShaderSymbolMode, ShaderSymbolRuntime,
18            ShaderSymbolRuntimeContext,
19        },
20    },
21};
22
23#[derive(Debug, Default, Clone)]
24pub struct ShaderRegion {
25    pub range: ShaderRange,
26    // Could add some ShaderRegionType::Condition / ShaderRegionType::User...
27    pub is_active: bool, // Is this region passing preprocess
28}
29
30impl ShaderRegion {
31    pub fn new(range: ShaderRange, is_active: bool) -> Self {
32        Self { range, is_active }
33    }
34}
35
36#[derive(Debug, Default, Clone)]
37pub struct ShaderPreprocessorContext {
38    defines: Vec<ShaderSymbol>,
39    include_handler: IncludeHandler,
40    dirty_files: HashSet<PathBuf>, // Dirty files that need to be recomputed no matter what.
41    depth: usize,
42}
43
44impl ShaderPreprocessorContext {
45    pub fn main(file_path: &Path, shader_params: ShaderContextParams) -> Self {
46        Self {
47            defines: shader_params
48                .defines
49                .iter()
50                .map(|(key, value)| ShaderSymbol {
51                    label: key.clone(),
52                    requirement: None,
53                    data: ShaderSymbolData::Macro {
54                        value: value.clone(),
55                    },
56                    mode: ShaderSymbolMode::RuntimeContext(ShaderSymbolRuntimeContext::new()),
57                })
58                .collect(),
59            include_handler: IncludeHandler::main(
60                &file_path,
61                shader_params.includes,
62                shader_params.path_remapping,
63            ),
64            dirty_files: HashSet::new(),
65            depth: 0,
66        }
67    }
68    pub fn mark_dirty(&mut self, file_path: PathBuf) {
69        self.dirty_files.insert(file_path);
70    }
71    pub fn search_path_in_includes(&mut self, path: &Path) -> Option<PathBuf> {
72        self.include_handler.search_path_in_includes(path)
73    }
74    pub fn push_directory_stack(&mut self, canonical_path: &Path) {
75        self.include_handler.push_directory_stack(canonical_path);
76    }
77    pub fn append_defines(&mut self, defines: Vec<ShaderPreprocessorDefine>) {
78        self.defines
79            .extend(defines.iter().map(|define| define.symbol.clone()));
80    }
81    pub fn increase_depth(&mut self) -> bool {
82        if self.depth < IncludeHandler::DEPTH_LIMIT {
83            self.depth += 1;
84            true
85        } else {
86            false
87        }
88    }
89    pub fn decrease_depth(&mut self) {
90        assert!(self.depth > 0, "Decreasing depth but zero.");
91        self.depth -= 1;
92    }
93    pub fn get_visited_count(&mut self, path: &Path) -> usize {
94        self.include_handler.get_visited_count(path)
95    }
96    pub fn is_dirty(&self, file_path: &Path, context: &ShaderPreprocessorContext) -> bool {
97        // Compare defines to determine if context is different.
98        // Check if we need to force an update aswell.
99        fn are_defines_equal(lhs: &Vec<ShaderSymbol>, rhs: &Vec<ShaderSymbol>) -> bool {
100            if lhs.len() != rhs.len() {
101                return false;
102            }
103            for lhs_symbol in lhs.iter() {
104                if rhs
105                    .iter()
106                    .find(|rhs_symbol| {
107                        lhs_symbol.label == rhs_symbol.label
108                            && match (&lhs_symbol.data, &rhs_symbol.data) {
109                                (
110                                    ShaderSymbolData::Macro { value: l_value },
111                                    ShaderSymbolData::Macro { value: r_value },
112                                ) => l_value == r_value,
113                                _ => false,
114                            }
115                    })
116                    .is_none()
117                {
118                    return false;
119                }
120            }
121            true
122        }
123        fn are_includes_equal(lhs: &HashSet<PathBuf>, rhs: &HashSet<PathBuf>) -> bool {
124            if lhs.len() != rhs.len() {
125                return false;
126            }
127            for lhs_symbol in lhs.iter() {
128                if rhs
129                    .iter()
130                    .find(|rhs_symbol| lhs_symbol.as_os_str() == rhs_symbol.as_os_str())
131                    .is_none()
132                {
133                    return false;
134                }
135            }
136            true
137        }
138        !are_defines_equal(&context.defines, &self.defines)
139            || !are_includes_equal(
140                context.include_handler.get_includes(),
141                self.include_handler.get_includes(),
142            )
143            || context.dirty_files.contains(file_path)
144    }
145    pub fn get_define_value(&self, name: &str) -> Option<String> {
146        self.defines
147            .iter()
148            .find(|symbol| *symbol.label == *name)
149            .map(|symbol| match &symbol.data {
150                ShaderSymbolData::Macro { value } => value.clone(),
151                _ => panic!("Expected ShaderSymbolData::Macro"),
152            })
153    }
154    pub fn get_defines(&self) -> &Vec<ShaderSymbol> {
155        &self.defines
156    }
157}
158
159#[derive(Debug, Clone)]
160pub struct ShaderPreprocessorInclude {
161    // TODO: move cache to symbol data
162    pub cache: Option<ShaderSymbols>,
163    symbol: ShaderSymbol,
164}
165
166#[derive(Debug, Clone)]
167pub struct ShaderPreprocessorDefine {
168    symbol: ShaderSymbol,
169}
170
171#[derive(Debug, Default, Clone, PartialEq, Eq)]
172pub enum ShaderPreprocessorMode {
173    #[default]
174    Default,
175    Once,
176    OnceVisited,
177}
178
179#[derive(Debug, Default, Clone)]
180pub struct ShaderPreprocessor {
181    pub context: ShaderPreprocessorContext, // Defines from includer files when included, or config.
182
183    pub includes: Vec<ShaderPreprocessorInclude>,
184    pub defines: Vec<ShaderPreprocessorDefine>,
185    pub regions: Vec<ShaderRegion>,
186    pub diagnostics: Vec<ShaderDiagnostic>, // preprocessor errors
187    pub mode: ShaderPreprocessorMode,
188}
189impl ShaderPreprocessorDefine {
190    pub fn new(name: String, range: ShaderFileRange, value: Option<String>) -> Self {
191        Self {
192            symbol: ShaderSymbol {
193                label: name.clone(),
194                requirement: None,
195                data: ShaderSymbolData::Macro {
196                    value: match &value {
197                        Some(value) => value.clone(),
198                        None => "".into(),
199                    },
200                },
201                mode: ShaderSymbolMode::Runtime(ShaderSymbolRuntime::global(
202                    range.file_path,
203                    range.range,
204                )),
205            },
206        }
207    }
208    pub fn get_file_path(&self) -> &Path {
209        &self.symbol.mode.unwrap_runtime().file_path
210    }
211    pub fn get_range(&self) -> &ShaderRange {
212        &self.symbol.mode.unwrap_runtime().range
213    }
214    pub fn get_name(&self) -> &String {
215        &self.symbol.label
216    }
217    pub fn get_value(&self) -> Option<&String> {
218        match &self.symbol.data {
219            ShaderSymbolData::Macro { value } => Some(value),
220            _ => None,
221        }
222    }
223}
224impl ShaderPreprocessorInclude {
225    pub fn new(relative_path: String, absolute_path: PathBuf, range: ShaderFileRange) -> Self {
226        Self {
227            cache: None,
228            symbol: ShaderSymbol {
229                label: relative_path,
230                requirement: None,
231                data: ShaderSymbolData::Include {
232                    target: absolute_path,
233                },
234                mode: ShaderSymbolMode::Runtime(ShaderSymbolRuntime::global(
235                    range.file_path,
236                    range.range,
237                )),
238            },
239        }
240    }
241    pub fn get_range(&self) -> &ShaderRange {
242        &self.symbol.mode.unwrap_runtime().range
243    }
244    pub fn get_file_range(&self) -> ShaderFileRange {
245        let runtime = self.symbol.mode.unwrap_runtime();
246        runtime.range.clone_into_file(runtime.file_path.clone())
247    }
248    pub fn get_relative_path(&self) -> &String {
249        &self.symbol.label
250    }
251    pub fn get_absolute_path(&self) -> &Path {
252        match &self.symbol.data {
253            ShaderSymbolData::Include { target } => &target,
254            _ => panic!("Expected ShaderSymbolData::Link"),
255        }
256    }
257    pub fn get_cache(&self) -> &ShaderSymbols {
258        self.cache.as_ref().unwrap()
259    }
260    pub fn get_cache_mut(&mut self) -> &mut ShaderSymbols {
261        self.cache.as_mut().unwrap()
262    }
263}
264
265impl ShaderPreprocessor {
266    pub fn new(context: ShaderPreprocessorContext) -> Self {
267        Self {
268            context: context,
269            includes: Vec::new(),
270            defines: Vec::new(),
271            regions: Vec::new(),
272            diagnostics: Vec::new(),
273            mode: ShaderPreprocessorMode::default(),
274        }
275    }
276    pub fn preprocess_symbols<'a>(
277        &'a self,
278        shader_symbols: &'a ShaderSymbolList,
279    ) -> ShaderSymbolListRef<'a> {
280        // Filter inactive regions symbols
281        let inactive_regions: Vec<&ShaderRegion> =
282            self.regions.iter().filter(|r| !r.is_active).collect();
283        let mut preprocessed_symbols =
284            shader_symbols.filter(move |_symbol_type, symbol| match &symbol.mode {
285                ShaderSymbolMode::Runtime(runtime) => inactive_regions
286                    .iter()
287                    .find(|r| r.range.contain_bounds(&runtime.range))
288                    .is_none(),
289                ShaderSymbolMode::RuntimeContext(_) => true, // Global range
290                ShaderSymbolMode::Intrinsic(_) => true,      // Global range
291            });
292        // Add defines
293        let mut define_symbols: Vec<&ShaderSymbol> =
294            self.defines.iter().map(|define| &define.symbol).collect();
295        // Add includes as symbol
296        let mut include_symbols: Vec<&ShaderSymbol> = self
297            .includes
298            .iter()
299            .map(|include| &include.symbol)
300            .collect();
301        preprocessed_symbols.macros.append(&mut define_symbols);
302        preprocessed_symbols.includes.append(&mut include_symbols);
303        preprocessed_symbols
304    }
305}