1use 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#[derive(Clone, Debug)]
17pub struct Config {
18 pub cpp_command: String,
20 pub cpp_options: Vec<String>,
22 pub flavor: Flavor,
24}
25
26impl Config {
27 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 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#[derive(Copy, Clone, PartialEq, Debug)]
60pub enum Flavor {
61 StdC11,
63 GnuC11,
65 ClangC11,
67}
68
69#[derive(Clone, Debug)]
71pub struct Parse {
72 pub source: String,
74 pub unit: TranslationUnit,
76}
77
78#[derive(Debug)]
79pub 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#[derive(Debug, Clone)]
111pub struct SyntaxError {
112 pub source: String,
114 pub line: usize,
116 pub column: usize,
118 pub offset: usize,
120 pub expected: HashSet<&'static str>,
122}
123
124impl SyntaxError {
125 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
160pub 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}