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