wee_peg/
lib.rs

1#![recursion_limit = "192"]
2
3#[macro_use]
4extern crate quote;
5extern crate codemap;
6extern crate codemap_diagnostic;
7
8use std::io;
9use std::io::prelude::*;
10use std::path::{Path, PathBuf};
11use std::convert::AsRef;
12use std::fs::File;
13use std::process::exit;
14use std::env;
15
16use codemap::{ CodeMap, Span };
17use codemap_diagnostic::{ Diagnostic, Level, SpanLabel, SpanStyle, Emitter, ColorConfig };
18
19mod translate;
20mod grammar;
21
22struct PegCompiler {
23    codemap: CodeMap,
24    diagnostics: Vec<codemap_diagnostic::Diagnostic>
25}
26
27impl PegCompiler {
28    fn new() -> PegCompiler {
29        PegCompiler {
30            codemap: CodeMap::new(),
31            diagnostics: vec![],
32        }
33    }
34
35    fn has_error(&self) -> bool {
36        self.diagnostics.iter().any(|d| d.level == Level::Error || d.level == Level::Bug)
37    }
38
39    fn span_error(&mut self, error: String, span: Span, label: Option<String>) {
40        self.diagnostics.push(Diagnostic {
41            level: Level::Error,
42            message: error,
43            code: None,
44            spans: vec![SpanLabel { span, label, style: SpanStyle::Primary }]
45        });
46    }
47
48    fn span_warning(&mut self, error: String, span: Span, label: Option<String>) {
49        self.diagnostics.push(Diagnostic {
50            level: Level::Warning,
51            message: error,
52            code: None,
53            spans: vec![SpanLabel { span, label, style: SpanStyle::Primary }]
54        });
55    }
56
57    fn print_diagnostics(&mut self) {
58        if !self.diagnostics.is_empty() {
59            let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&self.codemap));
60            emitter.emit(&self.diagnostics[..]);
61            self.diagnostics.clear();
62        }
63    }
64
65    fn compile(&mut self, filename: String, input: String) -> Result<String, ()> {
66        let file = self.codemap.add_file(filename, input);
67
68        let ast_items = match grammar::items(&file.source(), file.span) {
69            Ok(g) => g,
70            Err(e) => {
71                self.span_error(
72                    "Error parsing language specification".to_owned(),
73                    file.span.subspan(e.offset as u64, e.offset as u64),
74                    Some(format!("{}", e))
75                );
76                return Err(())
77            }
78        };
79
80        let grammar_def = translate::Grammar::from_ast(self, ast_items)?;
81        let output_tokens = translate::compile_grammar(self, &grammar_def);
82
83        if self.has_error() {
84            Err(())
85        } else {
86            Ok(output_tokens?.to_string())
87        }
88    }
89}
90
91/// Compile a peg grammar to Rust source, printing errors to stderr
92pub fn compile(filename: String, input: String) -> Result<String, ()> {
93    let mut compiler = PegCompiler::new();
94    let result = compiler.compile(filename, input);
95    compiler.print_diagnostics();
96    result
97}
98
99/// Compile the PEG grammar in the specified filename to cargo's OUT_DIR.
100/// Errors are emitted to stderr and terminate the process.
101pub fn cargo_build<T: AsRef<Path> + ?Sized>(input_path: &T) {
102    let mut stderr = io::stderr();
103    let input_path = input_path.as_ref();
104
105    let mut peg_source = String::new();
106    if let Err(e) = File::open(input_path).and_then(|mut x| x.read_to_string(&mut peg_source)) {
107        writeln!(stderr, "Could not read PEG input file `{}`: {}", input_path.display(), e).unwrap();
108        exit(1);
109    }
110
111    println!("cargo:rerun-if-changed={}", input_path.display());
112
113    let mut compiler = PegCompiler::new();
114    let result = compiler.compile(input_path.to_string_lossy().into_owned(), peg_source);
115    compiler.print_diagnostics();
116
117    let rust_source = match result {
118        Ok(s) => s,
119        Err(()) => {
120            writeln!(stderr, "Error compiling PEG grammar").unwrap();
121            exit(1);
122        }
123    };
124
125    let out_dir: PathBuf = env::var_os("OUT_DIR").unwrap().into();
126    let rust_path = out_dir.join(input_path.file_name().unwrap()).with_extension("rs");
127
128    let mut output_file = File::create(&rust_path).unwrap();
129    output_file.write_all(rust_source.as_bytes()).unwrap();
130}