lang_c/
driver.rs

1//! Preprocess and parse C source file into an abstract syntax tree
2
3use std::collections::HashSet;
4use std::error;
5use std::fmt;
6use std::io;
7use std::path::Path;
8use std::process::Command;
9
10use ast::TranslationUnit;
11use env::Env;
12use loc;
13use parser::translation_unit;
14
15/// Parser configuration
16#[derive(Clone, Debug)]
17pub struct Config {
18    /// Command used to invoke C preprocessor
19    pub cpp_command: String,
20    /// Options to pass to the preprocessor program
21    pub cpp_options: Vec<String>,
22    /// Language flavor to parse
23    pub flavor: Flavor,
24}
25
26impl Config {
27    /// Use `gcc` as a pre-processor and enable gcc extensions
28    pub fn with_gcc() -> Config {
29        Config {
30            cpp_command: "gcc".into(),
31            cpp_options: vec!["-E".into()],
32            flavor: Flavor::GnuC11,
33        }
34    }
35
36    /// Use `clang` as a pre-processor and enable Clang extensions
37    pub fn with_clang() -> Config {
38        Config {
39            cpp_command: "clang".into(),
40            cpp_options: vec!["-E".into()],
41            flavor: Flavor::ClangC11,
42        }
43    }
44}
45
46impl Default for Config {
47    #[cfg(target_os = "macos")]
48    fn default() -> Config {
49        Self::with_clang()
50    }
51
52    #[cfg(not(target_os = "macos"))]
53    fn default() -> Config {
54        Self::with_gcc()
55    }
56}
57
58/// C language flavors
59#[derive(Copy, Clone, PartialEq, Debug)]
60pub enum Flavor {
61    /// Strict standard C11
62    StdC11,
63    /// Standard C11 with GNU extensions
64    GnuC11,
65    /// Standard C11 with Clang extensions
66    ClangC11,
67}
68
69/// Result of a successful parse
70#[derive(Clone, Debug)]
71pub struct Parse {
72    /// Pre-processed source text
73    pub source: String,
74    /// Root of the abstract syntax tree
75    pub unit: TranslationUnit,
76}
77
78#[derive(Debug)]
79/// Error type returned from `parse`
80pub enum Error {
81    PreprocessorError(io::Error),
82    SyntaxError(SyntaxError),
83}
84
85impl From<SyntaxError> for Error {
86    fn from(e: SyntaxError) -> Error {
87        Error::SyntaxError(e)
88    }
89}
90
91impl fmt::Display for Error {
92    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
93        match self {
94            &Error::PreprocessorError(ref e) => write!(fmt, "preprocessor error: {}", e),
95            &Error::SyntaxError(ref e) => write!(fmt, "syntax error: {}", e),
96        }
97    }
98}
99
100impl error::Error for Error {
101    fn description(&self) -> &str {
102        match self {
103            &Error::PreprocessorError(_) => "preprocessor error",
104            &Error::SyntaxError(_) => "syntax error",
105        }
106    }
107}
108
109/// Syntax error during parsing
110#[derive(Debug, Clone)]
111pub struct SyntaxError {
112    /// Pre-processed source text
113    pub source: String,
114    /// Line number in the preprocessed source
115    pub line: usize,
116    /// Column number in the preprocessed source
117    pub column: usize,
118    /// Byte position in the preproccessed source
119    pub offset: usize,
120    /// Tokens expected at the error location
121    pub expected: HashSet<&'static str>,
122}
123
124impl SyntaxError {
125    /// Quoted and comma-separated list of expected tokens
126    pub fn format_expected(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
127        let mut list = self.expected.iter().collect::<Vec<_>>();
128        list.sort();
129        for (i, t) in list.iter().enumerate() {
130            if i > 0 {
131                try!(write!(fmt, ", "));
132            }
133            try!(write!(fmt, "'{}'", t));
134        }
135
136        Ok(())
137    }
138
139    pub fn get_location(&self) -> (loc::Location, Vec<loc::Location>) {
140        loc::get_location_for_offset(&self.source, self.offset)
141    }
142}
143
144impl fmt::Display for SyntaxError {
145    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
146        let (loc, inc) = self.get_location();
147        try!(write!(
148            fmt,
149            "unexpected token at \"{}\" line {} column {}, expected ",
150            loc.file, loc.line, self.column
151        ));
152        try!(self.format_expected(fmt));
153        for loc in inc {
154            try!(write!(fmt, "\n  included from {}:{}", loc.file, loc.line));
155        }
156        Ok(())
157    }
158}
159
160/// Parse a C file
161pub fn parse<P: AsRef<Path>>(config: &Config, source: P) -> Result<Parse, Error> {
162    let processed = match preprocess(config, source.as_ref()) {
163        Ok(s) => s,
164        Err(e) => return Err(Error::PreprocessorError(e)),
165    };
166
167    Ok(try!(parse_preprocessed(config, processed)))
168}
169
170pub fn parse_preprocessed(config: &Config, source: String) -> Result<Parse, SyntaxError> {
171    let mut env = match config.flavor {
172        Flavor::StdC11 => Env::with_core(),
173        Flavor::GnuC11 => Env::with_gnu(),
174        Flavor::ClangC11 => Env::with_clang(),
175    };
176
177    match translation_unit(&source, &mut env) {
178        Ok(unit) => Ok(Parse {
179            source: source,
180            unit: unit,
181        }),
182        Err(err) => Err(SyntaxError {
183            source: source,
184            line: err.line,
185            column: err.column,
186            offset: err.offset,
187            expected: err.expected,
188        }),
189    }
190}
191
192fn preprocess(config: &Config, source: &Path) -> io::Result<String> {
193    let mut cmd = Command::new(&config.cpp_command);
194
195    for item in &config.cpp_options {
196        cmd.arg(item);
197    }
198
199    cmd.arg(source);
200
201    let output = try!(cmd.output());
202
203    if output.status.success() {
204        match String::from_utf8(output.stdout) {
205            Ok(s) => Ok(s),
206            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
207        }
208    } else {
209        match String::from_utf8(output.stderr) {
210            Ok(s) => Err(io::Error::new(io::ErrorKind::Other, s)),
211            Err(_) => Err(io::Error::new(
212                io::ErrorKind::Other,
213                "cpp error contains invalid utf-8",
214            )),
215        }
216    }
217}