1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright 2018 glsl-include Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

#[macro_use]
extern crate lazy_static;
extern crate regex;

use std::collections::BTreeMap;
use std::collections::BTreeSet;
use regex::Regex;

#[derive(Debug)]
pub enum Error {
    RecursiveInclude(String, String, Vec<String>),
    FileNotFound(Option<String>, String),
}

#[derive(Debug)]
pub struct Preprocessor<'a> {
    files: BTreeMap<&'a str, &'a str>,
    should_generate_source_map: bool,
}

#[derive(Debug)]
pub struct FileLine<'a> {
    pub file: Option<&'a str>,
    pub line: usize,
}

pub type SourceMap<'a> = Vec<FileLine<'a>>;

impl<'a> Preprocessor<'a> {
    pub fn new() -> Preprocessor<'a> {
        Preprocessor {
            files: BTreeMap::new(),
            should_generate_source_map: false,
        }
    }

    pub fn file(mut self, path: &'a str, src: &'a str) -> Self {
        self.files.insert(path, src);
        self
    }

    pub fn generate_source_map(mut self) -> Self {
        self.should_generate_source_map = true;
        self
    }

    pub fn run(&self, src: &'a str) -> Result<(String, Option<SourceMap<'a>>), Error> {
        self.run_raw(src)
            .map(|(result, source_map)| (result.join("\n"), source_map))
    }

    pub fn run_raw(&self, src: &'a str) -> Result<(Vec<&'a str>, Option<SourceMap<'a>>), Error> {
        let mut result = Vec::new();
        let mut source_map = Vec::new();
        self.run_recursive(
            None,
            src,
            &mut result,
            &mut source_map,
            &mut Vec::new(),
            &mut BTreeSet::new(),
        ).map(move |_| {
            (
                result,
                if self.should_generate_source_map {
                    Some(source_map)
                } else {
                    None
                },
            )
        })
    }

    fn run_recursive(
        &self,
        name: Option<&'a str>,
        src: &'a str,
        result: &mut Vec<&'a str>,
        source_map: &mut SourceMap<'a>,
        include_stack: &mut Vec<&'a str>,
        include_set: &mut BTreeSet<&'a str>,
    ) -> Result<(), Error> {
        lazy_static! {
            static ref INCLUDE_RE : Regex = Regex::new(r#"^\s*#\s*include\s+[<"](?P<file>.*)[>"]"#).expect("failed to compile INCLUDE_RE regex");
        }

        // iterate through each line in src, if it matches INCLUDE_RE, then recurse, otherwise,
        // push the line to the result buffer and continue
        for line in src.lines() {
            if let Some(caps) = INCLUDE_RE.captures(line) {
                // The following expect should be impossible, but write a nice message anyways
                let cap_match = caps.name("file")
                    .expect("Could not find capture group with name \"file\"");
                let file = cap_match.as_str();

                // if this file has already been included, continue to the next line
                if include_set.contains(&file) {
                    continue;
                }

                // if this file is already in our include stack, return Err
                if include_stack.contains(&file) {
                    let name = name.expect("impossible").to_string();
                    let line = line.to_string();
                    let stack = include_stack.into_iter().map(|s| s.to_string()).collect();
                    return Err(Error::RecursiveInclude(name, line, stack));
                }
                include_stack.push(&file);

                // the src may include files that haven't been specified with Preprocessor::file,
                match self.files.get(file) {
                    Some(content) => {
                        self.run_recursive(
                            Some(file),
                            content,
                            result,
                            source_map,
                            include_stack,
                            include_set,
                        )?;
                    }
                    None => {
                        let name = name.map(|s| s.to_string());
                        let file = file.to_string();
                        return Err(Error::FileNotFound(name, file));
                    }
                };
                include_stack.pop();
            } else {
                result.push(line);
                if self.should_generate_source_map {
                    source_map.push(FileLine {
                        file: name,
                        line: result.len(),
                    });
                }
            }
        }
        // We're done processing this file, if it has a name, add it to the include_set
        if let Some(name) = name {
            include_set.insert(name);
        }
        Ok(())
    }
}