1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use pest;
#[macro_use]
extern crate pest_derive;
use railroad as rr;

use pest::iterators::Pair;
use pest::Parser;

#[derive(Parser)]
#[grammar = "parser.pest"]
struct RRParser;

fn unescape(pair: &Pair<'_, Rule>) -> String {
    let s = pair.as_str();
    let mut result = String::with_capacity(s.len());
    let mut iter = s[1..s.len() - 1].chars();
    while let Some(ch) = iter.next() {
        result.push(match ch {
            '\\' => iter.next().expect("no escaped char?"),
            _ => ch,
        });
    }
    result
}

fn binary<F, T>(pair: Pair<'_, Rule>, f: F) -> Box<dyn rr::RailroadNode>
where
    T: rr::RailroadNode + 'static,
    F: FnOnce(Box<dyn rr::RailroadNode>, Pair<'_, Rule>) -> T,
{
    let mut inner = pair.into_inner();
    let node = make_node(inner.next().expect("pair cannot be empty"));
    if let Some(pair) = inner.next() {
        Box::new(f(node, pair))
    } else {
        node
    }
}

fn make_node(pair: Pair<'_, Rule>) -> Box<dyn rr::RailroadNode> {
    use Rule::*;
    match pair.as_rule() {
        term => Box::new(rr::Terminal::new(unescape(&pair))),
        nonterm => Box::new(rr::NonTerminal::new(unescape(&pair))),
        comment => Box::new(rr::Comment::new(unescape(&pair))),
        empty => Box::new(rr::Empty),
        sequence => Box::new(rr::Sequence::new(
            pair.into_inner().map(make_node).collect(),
        )),
        stack => Box::new(rr::Stack::new(pair.into_inner().map(make_node).collect())),
        choice => Box::new(rr::Choice::new(pair.into_inner().map(make_node).collect())),
        opt_expr => binary(pair, |node, _| rr::Optional::new(node)),
        rpt_expr => binary(pair, |first, second| {
            rr::Repeat::new(first, make_node(second))
        }),
        lbox_expr => binary(pair, |first, second| {
            rr::LabeledBox::new(first, make_node(second))
        }),
        _ => unreachable!(),
    }
}

fn start_to_end(root: Box<dyn rr::RailroadNode>) -> Box<dyn rr::RailroadNode> {
    Box::new(rr::Sequence::new(vec![
        Box::new(rr::SimpleStart),
        root,
        Box::new(rr::SimpleEnd),
    ]))
}

pub fn compile(
    src: &str,
) -> Result<(i64, i64, rr::Diagram<Box<dyn rr::RailroadNode>>), pest::error::Error<Rule>> {
    let mut result = RRParser::parse(Rule::input, src)?;
    let trees = result.next().expect("expected root_expr").into_inner();
    let mut trees: Vec<_> = trees.map(|p| start_to_end(make_node(p))).collect();
    let root = if trees.len() == 1 {
        trees.remove(0)
    } else {
        Box::new(rr::VerticalGrid::new(trees))
    };
    let dia = rr::Diagram::with_default_css(root);
    let width = (&dia as &dyn rr::RailroadNode).width();
    let height = (&dia as &dyn rr::RailroadNode).height();
    Ok((width, height, dia))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::fs;
    use std::io::Read;
    use std::path;

    #[test]
    fn examples_must_parse() {
        let home = env::var_os("CARGO_MANIFEST_DIR").unwrap();
        let mut exmpl_dir = path::PathBuf::from(home);
        exmpl_dir.push("examples");
        for path in fs::read_dir(exmpl_dir)
            .unwrap()
            .into_iter()
            .filter_map(|d| d.ok())
        {
            if let Some(filename) = path.file_name().to_str() {
                if filename.ends_with("diagram.txt") {
                    eprintln!("Compiling `{}`", filename);
                    let mut buffer = String::new();
                    fs::File::open(path.path())
                        .unwrap()
                        .read_to_string(&mut buffer)
                        .unwrap();
                    if let Err(e) = compile(&buffer) {
                        panic!("Failed to compile {}", e.with_path(filename));
                    }
                }
            }
        }
    }
}