1 //! Utilies for running in a build script.
2 
3 use atty;
4 use file_text::FileText;
5 use grammar::parse_tree as pt;
6 use grammar::repr as r;
7 use lalrpop_util::ParseError;
8 use lexer::intern_token;
9 use lr1;
10 use message::builder::InlineBuilder;
11 use message::{Content, Message};
12 use normalize;
13 use parser;
14 use rust::RustWrite;
15 use session::{ColorConfig, Session};
16 use sha2::{Digest, Sha256};
17 use term;
18 use tls::Tls;
19 use tok;
20 
21 use std::fs;
22 use std::io::{self, BufRead, Write};
23 use std::path::{Path, PathBuf};
24 use std::process::exit;
25 use std::rc::Rc;
26 
27 mod action;
28 mod fake_term;
29 
30 use self::fake_term::FakeTerminal;
31 
32 const LALRPOP_VERSION_HEADER: &'static str = concat!(
33     "// auto-generated: \"",
34     env!("CARGO_PKG_NAME"),
35     " ",
36     env!("CARGO_PKG_VERSION"),
37     "\""
38 );
39 
hash_file(file: &Path) -> io::Result<String>40 fn hash_file(file: &Path) -> io::Result<String> {
41     let mut file = try!(fs::File::open(&file));
42     let mut sha_256 = Sha256::new();
43     try!(io::copy(&mut file, &mut sha_256));
44 
45     let mut hash_str = "// sha256: ".to_owned();
46     for byte in sha_256.result() {
47         hash_str.push_str(&format!("{:x}", byte));
48     }
49     Ok(hash_str)
50 }
51 
process_dir<P: AsRef<Path>>(session: Rc<Session>, root_dir: P) -> io::Result<()>52 pub fn process_dir<P: AsRef<Path>>(session: Rc<Session>, root_dir: P) -> io::Result<()> {
53     let lalrpop_files = try!(lalrpop_files(root_dir));
54     for lalrpop_file in lalrpop_files {
55         try!(process_file(session.clone(), lalrpop_file));
56     }
57     Ok(())
58 }
59 
process_file<P: AsRef<Path>>(session: Rc<Session>, lalrpop_file: P) -> io::Result<()>60 pub fn process_file<P: AsRef<Path>>(session: Rc<Session>, lalrpop_file: P) -> io::Result<()> {
61     let lalrpop_file = lalrpop_file.as_ref();
62     let rs_file = try!(resolve_rs_file(&session, lalrpop_file));
63     let report_file = try!(resolve_report_file(&session, lalrpop_file));
64     process_file_into(session, lalrpop_file, &rs_file, &report_file)
65 }
66 
resolve_rs_file(session: &Session, lalrpop_file: &Path) -> io::Result<PathBuf>67 fn resolve_rs_file(session: &Session, lalrpop_file: &Path) -> io::Result<PathBuf> {
68     gen_resolve_file(session, lalrpop_file, "rs")
69 }
70 
resolve_report_file(session: &Session, lalrpop_file: &Path) -> io::Result<PathBuf>71 fn resolve_report_file(session: &Session, lalrpop_file: &Path) -> io::Result<PathBuf> {
72     gen_resolve_file(session, lalrpop_file, "report")
73 }
74 
gen_resolve_file(session: &Session, lalrpop_file: &Path, ext: &str) -> io::Result<PathBuf>75 fn gen_resolve_file(session: &Session, lalrpop_file: &Path, ext: &str) -> io::Result<PathBuf> {
76     let in_dir = if let Some(ref d) = session.in_dir {
77         d.as_path()
78     } else {
79         Path::new(".")
80     };
81     let out_dir = if let Some(ref d) = session.out_dir {
82         d.as_path()
83     } else {
84         in_dir
85     };
86 
87     // If the lalrpop file is not in in_dir, the result is that the
88     // .rs file is created in the same directory as the lalrpop file
89     // for compatibility reasons
90     Ok(out_dir
91         .join(lalrpop_file.strip_prefix(&in_dir).unwrap_or(lalrpop_file))
92         .with_extension(ext))
93 }
94 
process_file_into( session: Rc<Session>, lalrpop_file: &Path, rs_file: &Path, report_file: &Path, ) -> io::Result<()>95 fn process_file_into(
96     session: Rc<Session>,
97     lalrpop_file: &Path,
98     rs_file: &Path,
99     report_file: &Path,
100 ) -> io::Result<()> {
101     session.emit_rerun_directive(lalrpop_file);
102     if session.force_build || try!(needs_rebuild(&lalrpop_file, &rs_file)) {
103         log!(
104             session,
105             Informative,
106             "processing file `{}`",
107             lalrpop_file.to_string_lossy()
108         );
109         if let Some(parent) = rs_file.parent() {
110             try!(fs::create_dir_all(parent));
111         }
112         try!(remove_old_file(&rs_file));
113 
114         // Load the LALRPOP source text for this file:
115         let file_text = Rc::new(try!(FileText::from_path(lalrpop_file.to_path_buf())));
116 
117         // Store the session and file-text in TLS -- this is not
118         // intended to be used in this high-level code, but it gives
119         // easy access to this information pervasively in the
120         // low-level LR(1) and grammar normalization code. This is
121         // particularly useful for error-reporting.
122         let _tls = Tls::install(session.clone(), file_text.clone());
123 
124         // Do the LALRPOP processing itself and write the resulting
125         // buffer into a file. We use a buffer so that if LR(1)
126         // generation fails at some point, we don't leave a partial
127         // file behind.
128         {
129             let grammar = try!(parse_and_normalize_grammar(&session, &file_text));
130             let buffer = try!(emit_recursive_ascent(&session, &grammar, &report_file));
131             let mut output_file = try!(fs::File::create(&rs_file));
132             try!(writeln!(output_file, "{}", LALRPOP_VERSION_HEADER));
133             try!(writeln!(output_file, "{}", try!(hash_file(&lalrpop_file))));
134             try!(output_file.write_all(&buffer));
135         }
136     }
137     Ok(())
138 }
139 
remove_old_file(rs_file: &Path) -> io::Result<()>140 fn remove_old_file(rs_file: &Path) -> io::Result<()> {
141     match fs::remove_file(rs_file) {
142         Ok(()) => Ok(()),
143         Err(e) => {
144             // Unix reports NotFound, Windows PermissionDenied!
145             match e.kind() {
146                 io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied => Ok(()),
147                 _ => Err(e),
148             }
149         }
150     }
151 }
152 
needs_rebuild(lalrpop_file: &Path, rs_file: &Path) -> io::Result<bool>153 fn needs_rebuild(lalrpop_file: &Path, rs_file: &Path) -> io::Result<bool> {
154     match fs::File::open(&rs_file) {
155         Ok(rs_file) => {
156             let mut version_str = String::new();
157             let mut hash_str = String::new();
158 
159             let mut f = io::BufReader::new(rs_file);
160 
161             try!(f.read_line(&mut version_str));
162             try!(f.read_line(&mut hash_str));
163 
164             Ok(hash_str.trim() != try!(hash_file(&lalrpop_file))
165                 || version_str.trim() != LALRPOP_VERSION_HEADER)
166         }
167         Err(e) => match e.kind() {
168             io::ErrorKind::NotFound => Ok(true),
169             _ => Err(e),
170         },
171     }
172 }
173 
lalrpop_files<P: AsRef<Path>>(root_dir: P) -> io::Result<Vec<PathBuf>>174 fn lalrpop_files<P: AsRef<Path>>(root_dir: P) -> io::Result<Vec<PathBuf>> {
175     let mut result = vec![];
176     for entry in try!(fs::read_dir(root_dir)) {
177         let entry = try!(entry);
178         let file_type = try!(entry.file_type());
179 
180         let path = entry.path();
181 
182         if file_type.is_dir() {
183             result.extend(try!(lalrpop_files(&path)));
184         }
185 
186         if file_type.is_file()
187             && path.extension().is_some()
188             && path.extension().unwrap() == "lalrpop"
189         {
190             result.push(path);
191         }
192     }
193     Ok(result)
194 }
195 
parse_and_normalize_grammar(session: &Session, file_text: &FileText) -> io::Result<r::Grammar>196 fn parse_and_normalize_grammar(session: &Session, file_text: &FileText) -> io::Result<r::Grammar> {
197     let grammar = match parser::parse_grammar(file_text.text()) {
198         Ok(grammar) => grammar,
199 
200         Err(ParseError::InvalidToken { location }) => {
201             let ch = file_text.text()[location..].chars().next().unwrap();
202             report_error(
203                 &file_text,
204                 pt::Span(location, location),
205                 &format!("invalid character `{}`", ch),
206             );
207         }
208 
209         Err(ParseError::UnrecognizedToken {
210             token: None,
211             expected: _,
212         }) => {
213             let len = file_text.text().len();
214             report_error(
215                 &file_text,
216                 pt::Span(len, len),
217                 &format!("unexpected end of file"),
218             );
219         }
220 
221         Err(ParseError::UnrecognizedToken {
222             token: Some((lo, _, hi)),
223             expected,
224         }) => {
225             let _ = expected; // didn't implement this yet :)
226             let text = &file_text.text()[lo..hi];
227             report_error(
228                 &file_text,
229                 pt::Span(lo, hi),
230                 &format!("unexpected token: `{}`", text),
231             );
232         }
233 
234         Err(ParseError::ExtraToken { token: (lo, _, hi) }) => {
235             let text = &file_text.text()[lo..hi];
236             report_error(
237                 &file_text,
238                 pt::Span(lo, hi),
239                 &format!("extra token at end of input: `{}`", text),
240             );
241         }
242 
243         Err(ParseError::User { error }) => {
244             let string = match error.code {
245                 tok::ErrorCode::UnrecognizedToken => "unrecognized token",
246                 tok::ErrorCode::UnterminatedEscape => "unterminated escape; missing '`'?",
247                 tok::ErrorCode::UnrecognizedEscape => {
248                     "unrecognized escape; only \\n, \\r, \\t, \\\" and \\\\ are recognized"
249                 }
250                 tok::ErrorCode::UnterminatedStringLiteral => {
251                     "unterminated string literal; missing `\"`?"
252                 }
253                 tok::ErrorCode::UnterminatedCharacterLiteral => {
254                     "unterminated character literal; missing `'`?"
255                 }
256                 tok::ErrorCode::UnterminatedAttribute => "unterminated #! attribute; missing `]`?",
257                 tok::ErrorCode::ExpectedStringLiteral => "expected string literal; missing `\"`?",
258                 tok::ErrorCode::UnterminatedCode => {
259                     "unterminated code block; perhaps a missing `;`, `)`, `]` or `}`?"
260                 }
261             };
262 
263             report_error(
264                 &file_text,
265                 pt::Span(error.location, error.location + 1),
266                 string,
267             )
268         }
269     };
270 
271     match normalize::normalize(session, grammar) {
272         Ok(grammar) => Ok(grammar),
273         Err(error) => report_error(&file_text, error.span, &error.message),
274     }
275 }
276 
report_error(file_text: &FileText, span: pt::Span, message: &str) -> !277 fn report_error(file_text: &FileText, span: pt::Span, message: &str) -> ! {
278     println!("{} error: {}", file_text.span_str(span), message);
279 
280     let out = io::stderr();
281     let mut out = out.lock();
282     file_text.highlight(span, &mut out).unwrap();
283 
284     exit(1);
285 }
286 
report_messages(messages: Vec<Message>) -> term::Result<()>287 fn report_messages(messages: Vec<Message>) -> term::Result<()> {
288     let builder = InlineBuilder::new().begin_paragraphs();
289     let builder = messages
290         .into_iter()
291         .fold(builder, |b, m| b.push(Box::new(m)));
292     let content = builder.end().end();
293     report_content(&*content)
294 }
295 
report_content(content: &Content) -> term::Result<()>296 fn report_content(content: &Content) -> term::Result<()> {
297     // FIXME -- can we query the size of the terminal somehow?
298     let canvas = content.emit_to_canvas(80);
299 
300     let try_colors = match Tls::session().color_config {
301         ColorConfig::Yes => true,
302         ColorConfig::No => false,
303         ColorConfig::IfTty => atty::is(atty::Stream::Stdout),
304     };
305 
306     if try_colors {
307         if let Some(mut stdout) = term::stdout() {
308             return canvas.write_to(&mut *stdout);
309         }
310     }
311 
312     let stdout = io::stdout();
313     let mut stdout = FakeTerminal::new(stdout.lock());
314     canvas.write_to(&mut stdout)
315 }
316 
emit_module_attributes<W: Write>( grammar: &r::Grammar, rust: &mut RustWrite<W>, ) -> io::Result<()>317 fn emit_module_attributes<W: Write>(
318     grammar: &r::Grammar,
319     rust: &mut RustWrite<W>,
320 ) -> io::Result<()> {
321     rust.write_module_attributes(grammar)
322 }
323 
emit_uses<W: Write>(grammar: &r::Grammar, rust: &mut RustWrite<W>) -> io::Result<()>324 fn emit_uses<W: Write>(grammar: &r::Grammar, rust: &mut RustWrite<W>) -> io::Result<()> {
325     rust.write_uses("", grammar)
326 }
327 
emit_recursive_ascent( session: &Session, grammar: &r::Grammar, report_file: &Path, ) -> io::Result<Vec<u8>>328 fn emit_recursive_ascent(
329     session: &Session,
330     grammar: &r::Grammar,
331     report_file: &Path,
332 ) -> io::Result<Vec<u8>> {
333     let mut rust = RustWrite::new(vec![]);
334 
335     // We generate a module structure like this:
336     //
337     // ```
338     // mod <output-file> {
339     //     // For each public symbol:
340     //     pub fn parse_XYZ();
341     //     mod __XYZ { ... }
342     //
343     //     // For each bit of action code:
344     //     <action-code>
345     // }
346     // ```
347     //
348     // Note that the action code goes in the outer module.  This is
349     // intentional because it means that the foo.lalrpop file serves
350     // as a module in the rust hierarchy, so if the action code
351     // includes things like `super::` it will resolve in the natural
352     // way.
353 
354     try!(emit_module_attributes(grammar, &mut rust));
355     try!(emit_uses(grammar, &mut rust));
356 
357     if grammar.start_nonterminals.is_empty() {
358         println!("Error: no public symbols declared in grammar");
359         exit(1);
360     }
361 
362     for (user_nt, start_nt) in &grammar.start_nonterminals {
363         // We generate these, so there should always be exactly 1
364         // production. Otherwise the LR(1) algorithm doesn't know
365         // where to stop!
366         assert_eq!(grammar.productions_for(start_nt).len(), 1);
367 
368         log!(
369             session,
370             Verbose,
371             "Building states for public nonterminal `{}`",
372             user_nt
373         );
374 
375         let _lr1_tls = lr1::Lr1Tls::install(grammar.terminals.clone());
376 
377         let lr1result = lr1::build_states(&grammar, start_nt.clone());
378         if session.emit_report {
379             let mut output_report_file = try!(fs::File::create(&report_file));
380             try!(lr1::generate_report(&mut output_report_file, &lr1result));
381         }
382 
383         let states = match lr1result {
384             Ok(states) => states,
385             Err(error) => {
386                 let messages = lr1::report_error(&grammar, &error);
387                 let _ = report_messages(messages);
388                 exit(1) // FIXME -- propagate up instead of calling `exit`
389             }
390         };
391 
392         match grammar.algorithm.codegen {
393             r::LrCodeGeneration::RecursiveAscent => try!(lr1::codegen::ascent::compile(
394                 &grammar,
395                 user_nt.clone(),
396                 start_nt.clone(),
397                 &states,
398                 "super",
399                 &mut rust,
400             )),
401             r::LrCodeGeneration::TableDriven => try!(lr1::codegen::parse_table::compile(
402                 &grammar,
403                 user_nt.clone(),
404                 start_nt.clone(),
405                 &states,
406                 "super",
407                 &mut rust,
408             )),
409 
410             r::LrCodeGeneration::TestAll => try!(lr1::codegen::test_all::compile(
411                 &grammar,
412                 user_nt.clone(),
413                 start_nt.clone(),
414                 &states,
415                 &mut rust,
416             )),
417         }
418 
419         rust!(
420             rust,
421             "{}use self::{}parse{}::{}Parser;",
422             grammar.nonterminals[&user_nt].visibility,
423             grammar.prefix,
424             start_nt,
425             user_nt
426         );
427     }
428 
429     if let Some(ref intern_token) = grammar.intern_token {
430         try!(intern_token::compile(&grammar, intern_token, &mut rust));
431         rust!(rust, "pub use self::{}intern_token::Token;", grammar.prefix);
432     }
433 
434     try!(action::emit_action_code(grammar, &mut rust));
435 
436     try!(emit_to_triple_trait(grammar, &mut rust));
437 
438     Ok(rust.into_inner())
439 }
440 
emit_to_triple_trait<W: Write>(grammar: &r::Grammar, rust: &mut RustWrite<W>) -> io::Result<()>441 fn emit_to_triple_trait<W: Write>(grammar: &r::Grammar, rust: &mut RustWrite<W>) -> io::Result<()> {
442     #![allow(non_snake_case)]
443 
444     let L = grammar.types.terminal_loc_type();
445     let T = grammar.types.terminal_token_type();
446     let E = grammar.types.error_type();
447 
448     let parse_error = format!(
449         "{p}lalrpop_util::ParseError<{L}, {T}, {E}>",
450         p = grammar.prefix,
451         L = L,
452         T = T,
453         E = E,
454     );
455 
456     let mut user_type_parameters = String::new();
457     for type_parameter in &grammar.type_parameters {
458         user_type_parameters.push_str(&format!("{}, ", type_parameter));
459     }
460 
461     rust!(rust, "");
462     rust!(
463         rust,
464         "pub trait {}ToTriple<{}> {{",
465         grammar.prefix,
466         user_type_parameters,
467     );
468     rust!(
469         rust,
470         "fn to_triple(value: Self) -> Result<({L},{T},{L}), {parse_error}>;",
471         L = L,
472         T = T,
473         parse_error = parse_error,
474     );
475     rust!(rust, "}}");
476 
477     rust!(rust, "");
478     if grammar.types.opt_terminal_loc_type().is_some() {
479         rust!(
480             rust,
481             "impl<{utp}> {p}ToTriple<{utp}> for ({L}, {T}, {L}) {{",
482             p = grammar.prefix,
483             utp = user_type_parameters,
484             L = L,
485             T = T,
486         );
487         rust!(
488             rust,
489             "fn to_triple(value: Self) -> Result<({L},{T},{L}), {parse_error}> {{",
490             L = L,
491             T = T,
492             parse_error = parse_error,
493         );
494         rust!(rust, "Ok(value)");
495         rust!(rust, "}}");
496         rust!(rust, "}}");
497 
498         rust!(
499             rust,
500             "impl<{utp}> {p}ToTriple<{utp}> for Result<({L}, {T}, {L}), {E}> {{",
501             utp = user_type_parameters,
502             p = grammar.prefix,
503             L = L,
504             T = T,
505             E = E,
506         );
507         rust!(
508             rust,
509             "fn to_triple(value: Self) -> Result<({L},{T},{L}), {parse_error}> {{",
510             L = L,
511             T = T,
512             parse_error = parse_error,
513         );
514         rust!(rust, "match value {{");
515         rust!(rust, "Ok(v) => Ok(v),");
516         rust!(rust, "Err(error) => Err({p}lalrpop_util::ParseError::User {{ error }}),",
517               p = grammar.prefix);
518         rust!(rust, "}}"); // match
519         rust!(rust, "}}");
520         rust!(rust, "}}");
521     } else {
522         rust!(
523             rust,
524             "impl<{utp}> {p}ToTriple<{utp}> for {T} {{",
525             utp = user_type_parameters,
526             p = grammar.prefix,
527             T = T,
528         );
529         rust!(
530             rust,
531             "fn to_triple(value: Self) -> Result<((),{T},()), {parse_error}> {{",
532             T = T,
533             parse_error = parse_error,
534         );
535         rust!(rust, "Ok(((), value, ()))");
536         rust!(rust, "}}");
537         rust!(rust, "}}");
538 
539         rust!(
540             rust,
541             "impl<{utp}> {p}ToTriple<{utp}> for Result<({T}),{E}> {{",
542             utp = user_type_parameters,
543             p = grammar.prefix,
544             T = T,
545             E = E,
546         );
547         rust!(
548             rust,
549             "fn to_triple(value: Self) -> Result<((),{T},()), {parse_error}> {{",
550             T = T,
551             parse_error = parse_error,
552         );
553         rust!(rust, "match value {{");
554         rust!(rust, "Ok(v) => Ok(((), v, ())),");
555         rust!(rust, "Err(error) => Err({p}lalrpop_util::ParseError::User {{ error }}),",
556               p = grammar.prefix);
557         rust!(rust, "}}"); // match
558         rust!(rust, "}}"); // fn
559         rust!(rust, "}}"); // impl
560     }
561 
562     Ok(())
563 }
564