1use std::fs::File;
2use std::io::{ErrorKind, Write};
3use std::panic::PanicInfo;
4use std::path::{Path, PathBuf};
5use std::process::{exit, Command, Output};
6use std::time::Instant;
7use std::{fs, io, panic};
8
9use atty::Stream;
10use clap::CommandFactory;
11use clap::FromArgMatches;
12use clap::Parser as CliParser;
13use regex::bytes::Regex;
14use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
15
16use crate::core::{CompilerSettings, Error, Parser};
17
18const OUT_DIR_FILES: &[(&str, &[u8])] = &[
20 ("build/runtime/mod.rs", include_bytes!("runtime/mod.rs")),
21 (
22 "build/runtime/context.rs",
23 include_bytes!("runtime/context.rs"),
24 ),
25 (
26 "build/runtime/grammar.rs",
27 include_bytes!("runtime/grammar.rs"),
28 ),
29 ("build/runtime/input.rs", include_bytes!("runtime/input.rs")),
30 (
31 "build/runtime/result.rs",
32 include_bytes!("runtime/result.rs"),
33 ),
34 (
35 "build/runtime/buffered_iter.rs",
36 include_bytes!("runtime/buffered_iter.rs"),
37 ),
38 (
39 "build/runtime/array_vec.rs",
40 include_bytes!("runtime/array_vec.rs"),
41 ),
42 (
43 "build/runtime/small_vec.rs",
44 include_bytes!("runtime/small_vec.rs"),
45 ),
46 ("build/runtime/stack.rs", include_bytes!("runtime/stack.rs")),
47 ("build/runtime/cache.rs", include_bytes!("runtime/cache.rs")),
48 ("build/runtime/refc.rs", include_bytes!("runtime/refc.rs")),
49 ("build/harness.rs", include_bytes!("include/harness.rs")),
50 ("build/loader.js", include_bytes!("include/loader.js")),
51 ("loader.d.ts", include_bytes!("include/loader.d.ts")),
52 (".gitignore", include_bytes!("include/gitignore")),
53];
54
55pub fn parse_args() -> Cli {
56 let command = (Cli::command() as clap::Command).color(clap::ColorChoice::Auto);
57 Cli::from_arg_matches(&command.get_matches()).unwrap()
58}
59
60pub fn run(args: Cli) {
61 let context = Context::new(args);
62 context.run();
63}
64
65pub fn setup_panic_hook() {
68 let default_hook = panic::take_hook();
69 panic::set_hook(Box::new(move |info| panic_hook(info, &default_hook)));
70}
71
72fn panic_hook(info: &PanicInfo, default_hook: &dyn Fn(&PanicInfo)) {
73 let color = if atty::is(Stream::Stderr) {
74 termcolor::ColorChoice::Auto
75 } else {
76 termcolor::ColorChoice::Never
77 };
78
79 let mut red = ColorSpec::new();
80 red.set_fg(Some(Color::Red));
81
82 let mut reset = ColorSpec::new();
83 reset.set_reset(true);
84
85 let mut stderr = StandardStream::stderr(color);
86 let _ = stderr.set_color(&red);
87 let _ = writeln!(
88 stderr,
89 "Fatal internal error, this is a bug. Please report this to the developers"
90 );
91 let _ = stderr.set_color(&reset);
92
93 default_hook(info);
94}
95
96#[derive(CliParser)]
97#[clap(author, version, about)]
98pub struct Cli {
99 pub grammar: PathBuf,
101
102 #[clap(short, long)]
104 pub out_dir: Option<PathBuf>,
105
106 #[clap(short, long)]
108 pub interactive: bool,
109
110 #[clap(long)]
112 pub state_opt: bool,
113}
114
115struct Context {
116 opts: Cli,
117 stderr: StandardStream,
118 active_indicator: bool,
120 start: Instant,
122}
123
124impl Context {
125 fn new(cli: Cli) -> Self {
126 let stderr_color = if atty::is(Stream::Stderr) {
128 termcolor::ColorChoice::Auto
129 } else {
130 termcolor::ColorChoice::Never
131 };
132
133 let stderr = StandardStream::stderr(stderr_color);
134
135 Self {
136 stderr,
137 opts: cli,
138 active_indicator: false,
139 start: Instant::now(),
140 }
141 }
142
143 fn run(mut self) {
144 self.set_indicator("Checking environment");
145 self.check_node();
146 self.check_grammar();
147
148 if self.opts.interactive {
149 self.check_rust();
150 }
151
152 self.set_indicator("Setting up output");
153 self.create_out_dir();
154 self.populate_out_dir();
155
156 self.clear_indicator();
157 self.execute_grammar();
158
159 self.set_indicator("Generating parser");
160 let parser = self.load_parser();
161 self.generate_code(parser);
162
163 if self.opts.interactive {
164 self.set_indicator("Compiling");
165 self.compile();
166
167 self.print_ready();
168 self.execute();
169 } else {
170 self.print_ready();
171 }
172 }
173
174 fn print_ready(&mut self) {
175 self.println(format!("Parser built in {:.1?}", self.start.elapsed()));
176 }
177
178 fn load_parser(&mut self) -> Parser {
180 let ir = match fs::read(self.ir_file()) {
181 Ok(ir) => ir,
182 Err(err) => {
183 self.exit_with_error(format!("Could not read IR: {}", err));
184 }
185 };
186
187 let mut settings = CompilerSettings::normal();
188 settings.state_optimization = self.opts.state_opt;
189
190 match Parser::load(&ir, settings) {
191 Ok(parser) => parser,
192 Err(Error::Load(message)) => self.exit_with_error(message),
193 Err(Error::LeftRecursive(left_recursive)) => {
194 self.print_error_heading();
195
196 if left_recursive.len() == 1 {
197 self.print("Ill-formed grammar, ");
198 self.print_color(Color::Yellow, false);
199 self.print(left_recursive.iter().next().unwrap());
200 self.print_reset();
201 self.println(" is left-recursive");
202 } else {
203 self.print("Ill-formed grammar, the following rules are left-recursive: ");
204
205 for (i, rule) in left_recursive.iter().enumerate() {
206 if i != 0 {
207 self.print(", ");
208 }
209
210 self.print_color(Color::Yellow, false);
211 self.print(rule);
212 self.print_reset();
213 }
214
215 self.println("");
216 }
217
218 exit(1);
219 }
220 }
221 }
222
223 fn generate_code(&mut self, parser: Parser) {
225 let code = parser.generate();
226
227 if let Err(err) = fs::write(self.parser_file(), code) {
228 self.exit_with_error(format!("Could not write generated code: {}", err));
229 }
230 }
231
232 fn compile(&mut self) {
234 let result = Command::new("rustc")
235 .args(["--edition", "2021"])
236 .args(["-C", "opt-level=3"])
237 .arg("-o")
238 .arg(self.executable_file())
239 .arg(self.harness_file())
240 .output();
241
242 let result = match result {
243 Ok(result) => result,
244 Err(err) => {
245 self.exit_with_error(format!("Could not compile parser: {}", err));
246 }
247 };
248
249 if !result.status.success() {
250 self.exit_with_error_and_output("Could not compile parser", &result);
251 }
252 }
253
254 fn execute(&mut self) {
256 let result = Command::new(self.executable_file()).status();
257
258 let status = match result {
259 Ok(result) => result,
260 Err(err) => {
261 self.exit_with_error(format!("Could not launch parser: {}", err));
262 }
263 };
264
265 if !status.success() {
266 if let Some(status) = status.code() {
267 self.exit_with_error(format!("Parser exited with status {}", status));
268 } else {
269 self.exit_with_error("Parser exited with unknown status");
270 }
271 }
272 }
273
274 fn execute_grammar(&mut self) {
276 if let Err(err) = self.execute_grammar_unhandled() {
277 self.exit_with_error(format!("Could not run grammar script: {}", err));
278 }
279 }
280
281 fn execute_grammar_unhandled(&mut self) -> io::Result<()> {
282 let grammar_path = self.opts.grammar.canonicalize()?;
283 let loader_path = self.loader_file();
284 let ir_path = self.ir_file();
285
286 let status = Command::new("node")
287 .env("PEG_PACK_GRAMMAR", grammar_path)
288 .env("PEG_PACK_IR", ir_path)
289 .arg(loader_path)
290 .status()?;
291
292 if !status.success() {
293 if let Some(status) = status.code() {
294 self.exit_with_error(format!("Grammar script exited with status {}", status));
295 } else {
296 self.exit_with_error("Grammar script exited with unknown status");
297 }
298 }
299
300 Ok(())
301 }
302
303 fn check_grammar(&mut self) {
305 let grammar = &self.opts.grammar;
306 let display = grammar.display();
307
308 if let Err(err) = File::open(grammar) {
309 if err.kind() == ErrorKind::NotFound {
310 self.exit_with_error(format!("Grammar file does not exist ({})", display));
311 } else if err.kind() == ErrorKind::PermissionDenied {
312 self.exit_with_error(format!(
313 "Insufficient permissions to access grammar file ({})",
314 display
315 ));
316 } else {
317 self.exit_with_error(format!(
318 "Could not open grammar file ({}): {}",
319 display, err
320 ));
321 }
322 }
323
324 if !grammar.is_file() {
325 self.exit_with_error(format!("Grammar was not a file ({})", display));
326 }
327 }
328
329 fn create_out_dir(&mut self) {
331 let out_dir = self.out_dir();
332 let display = out_dir.display();
333
334 if out_dir.exists() && !out_dir.is_dir() {
335 self.exit_with_error(format!("Output directory is not a directory ({})", display));
336 }
337
338 if out_dir.exists() {
339 if let Err(err) = fs::remove_dir_all(out_dir) {
340 self.exit_with_error(format!("Could not remove old output directory: {}", err));
341 }
342 }
343
344 if let Err(err) = fs::create_dir(out_dir) {
345 if err.kind() == ErrorKind::NotFound {
346 self.exit_with_error(format!(
347 "Parent of output directory does not exist ({})",
348 display
349 ));
350 } else if err.kind() == ErrorKind::PermissionDenied {
351 self.exit_with_error(format!(
352 "Insufficient permissions to create output directory ({})",
353 display
354 ));
355 } else {
356 self.exit_with_error(format!(
357 "Could not create output directory ({}): {}",
358 display, err
359 ));
360 }
361 }
362 }
363
364 fn populate_out_dir(&mut self) {
366 if let Err(err) = self.populate_out_dir_unhandled() {
367 self.exit_with_error(format!("Error populating output directory: {}", err));
368 }
369 }
370
371 fn populate_out_dir_unhandled(&mut self) -> io::Result<()> {
372 let out_dir = self.out_dir();
373
374 for (name, data) in OUT_DIR_FILES {
375 let path = out_dir.join(name);
376 assert!(path.starts_with(out_dir));
377
378 let parent = path.parent().unwrap();
379 fs::create_dir_all(parent)?;
380
381 fs::write(path, data)?;
382 }
383
384 Ok(())
385 }
386
387 fn check_node(&mut self) {
389 let command = Command::new("node").arg("--version").output();
390 let version_regex = Regex::new(r"^v(\d+)\.").unwrap();
391
392 self.check_command_installation(
393 command,
394 "NodeJS",
395 "https://nodejs.org",
396 version_regex,
397 16,
398 ">=16.0.0",
399 );
400 }
401
402 fn check_rust(&mut self) {
404 let command = Command::new("rustc")
405 .args(["+stable", "--version"])
406 .output();
407 let version_regex = Regex::new(r"^rustc 1\.(\d+)\.").unwrap();
408
409 self.check_command_installation(
410 command,
411 "Rust",
412 "https://rustup.rs",
413 version_regex,
414 61,
415 "^1.61.0",
416 );
417 }
418
419 fn check_command_installation(
421 &mut self,
422 result: io::Result<Output>,
423 name: &str,
424 download_url: &str,
425 version_regex: Regex,
426 expected_version: u32,
427 expected_version_spec: &str,
428 ) {
429 let result = match result {
430 Ok(result) => result,
431 Err(err) => {
432 if err.kind() == ErrorKind::NotFound {
433 self.exit_with_error(format!(
434 "{} was not found. Check that it is installed and added to PATH ({})",
435 name, download_url
436 ));
437 } else if err.kind() == ErrorKind::PermissionDenied {
438 self.exit_with_error(format!(
439 "Insufficient permission to run {}. Reconfigure your installation, or run with more permissions",
440 name
441 ));
442 } else {
443 self.exit_with_error(format!("Could not run {}: {}", name, err));
444 }
445 }
446 };
447
448 if !result.status.success() {
449 self.exit_with_error_and_output(
450 format!("{} version check returned non-zero exit status", name),
451 &result,
452 );
453 }
454
455 let version = version_regex.captures(&result.stdout).and_then(|captures| {
456 let version_match = captures.get(1).unwrap();
457 let version_str = String::from_utf8_lossy(version_match.as_bytes());
458 version_str.parse::<u32>().ok()
459 });
460
461 match version {
462 Some(version) if version < expected_version => {
463 self.print_warn(format!(
464 "{} version ({}) out of date, expected {}",
465 name, version, expected_version_spec
466 ));
467 }
468 None => {
469 let version = String::from_utf8_lossy(&result.stdout);
470 self.print_warn(format!(
471 "Could not parse {} version ({})",
472 name,
473 version.trim()
474 ));
475 }
476 Some(_) => {}
477 }
478 }
479
480 fn executable_file(&self) -> PathBuf {
481 if cfg!(windows) {
482 self.out_dir().join("build/parser.exe")
483 } else {
484 self.out_dir().join("build/parser")
485 }
486 }
487
488 fn parser_file(&self) -> PathBuf {
489 self.out_dir().join("parser.rs")
490 }
491
492 fn harness_file(&self) -> PathBuf {
493 self.out_dir().join("build/harness.rs")
494 }
495
496 fn loader_file(&self) -> PathBuf {
497 self.out_dir().join("build/loader.js")
498 }
499
500 fn ir_file(&self) -> PathBuf {
501 self.out_dir().join("build/ir.json")
502 }
503
504 fn out_dir(&self) -> &Path {
505 self.opts
506 .out_dir
507 .as_ref()
508 .map(|buf| buf as &Path)
509 .unwrap_or_else(|| Path::new("peg-pack-out"))
510 }
511
512 fn set_indicator(&mut self, indicator: impl AsRef<str>) {
514 self.clear_indicator();
515
516 let mut color = ColorSpec::new();
517 color.set_italic(true);
518 color.set_fg(Some(Color::Blue));
519
520 let mut reset = ColorSpec::new();
521 reset.set_reset(true);
522
523 let _ = self.stderr.set_color(&color);
524 let _ = writeln!(self.stderr, "{}", indicator.as_ref());
525 let _ = self.stderr.set_color(&reset);
526
527 self.active_indicator = true;
528 }
529
530 fn clear_indicator(&mut self) {
532 if self.active_indicator && self.stderr.supports_color() {
533 let _ = write!(self.stderr, "\x1b[F\x1b[K");
534 }
535
536 self.active_indicator = false;
537 }
538
539 fn print_output(&mut self, output: &Output) {
541 self.print_output_stream("stdout", &output.stdout);
542 self.print_output_stream("stderr", &output.stderr);
543 }
544
545 fn print_output_stream(&mut self, name: &str, stream: &[u8]) {
546 if stream.is_empty() {
547 return;
548 }
549
550 let content = String::from_utf8_lossy(stream);
551
552 for line in content.lines() {
553 self.print_color(Color::Cyan, true);
554 self.print(name);
555 self.print(": ");
556 self.print_reset();
557 self.println(line);
558 }
559 }
560
561 fn exit_with_error(&mut self, message: impl AsRef<str>) -> ! {
563 self.print_error(message);
564 exit(1);
565 }
566
567 fn exit_with_error_and_output(&mut self, message: impl AsRef<str>, output: &Output) -> ! {
569 self.print_error(message);
570 self.print_output(output);
571 exit(1);
572 }
573
574 fn print_error(&mut self, message: impl AsRef<str>) {
575 self.print_error_heading();
576 self.println(message);
577 }
578
579 fn print_warn(&mut self, message: impl AsRef<str>) {
580 self.print_warn_heading();
581 self.println(message);
582 }
583
584 fn print_error_heading(&mut self) {
585 self.print_color(Color::Red, true);
586 self.print("error: ");
587 self.print_reset();
588 }
589
590 fn print_warn_heading(&mut self) {
591 self.print_color(Color::Yellow, true);
592 self.print("warn: ");
593 self.print_reset();
594 }
595
596 fn print(&mut self, message: impl AsRef<str>) {
597 self.clear_indicator();
598 let _ = write!(self.stderr, "{}", message.as_ref());
599 }
600
601 fn println(&mut self, message: impl AsRef<str>) {
602 self.clear_indicator();
603 let _ = writeln!(self.stderr, "{}", message.as_ref());
604 }
605
606 fn print_color(&mut self, color: Color, bold: bool) {
607 self.clear_indicator();
608 let mut color_spec = ColorSpec::new();
609 color_spec.set_fg(Some(color));
610 color_spec.set_bold(bold);
611 let _ = self.stderr.set_color(&color_spec);
612 }
613
614 fn print_reset(&mut self) {
615 self.clear_indicator();
616 let mut reset_color = ColorSpec::new();
617 reset_color.set_reset(true);
618 let _ = self.stderr.set_color(&reset_color);
619 }
620}