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)); } } } } } }