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
122
123
124
125
126
127
128
use lalrpop_util;
#[allow(dead_code)]
mod rules;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Rule {
pub pat: Match,
pub render: Rendering,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Match {
Loop(CharSet, Dirs, CharSet, Dirs, CharSet),
Step(CharSet, Dirs, CharSet, Dirs, CharSet),
Start(CharSet, Dirs, CharSet),
End(CharSet, Dirs, CharSet),
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Rendering {
pub draw: String,
pub attrs: Option<Vec<(String, String)>>,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum CharSet {
Char(char),
String(String),
Any,
}
#[allow(dead_code)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Unforgeable(UnusedMarker);
#[allow(dead_code)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct UnusedMarker;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Dir { N, NE, E, SE, S, SW, W, NW }
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Dirs(pub Vec<Dir>);
use self::Dir as D;
pub const ALL_DIRS: [Dir; 8] = [D::N, D::NE, D::E, D::SE, D::S, D::SW, D::W, D::NW];
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ParseError<'input>(lalrpop_util::ParseError<usize, (usize, &'input str), ()>);
pub fn parse_rules(s: &str) -> Result<Vec<Rule>, ParseError> {
rules::parse_Rules(s).map_err(|e| ParseError(e))
}
macro_rules! assert_ok {
($r: expr) => {
let r;
assert!({ r = $r; (r: Result<_, _>).is_ok() }, "result not ok; err: {:?}", r.unwrap_err());
}
}
#[cfg(test)]
pub(crate) const SAMPLE_GRAMMAR: &'static str = r#"
loop "|-/\" ANY '+' (N,S) "|" draw "M {C}";
loop "|-/\" ANY '+' (E,W) "-" draw "M {C}";
# ‘-‘, ‘|‘, and ‘+‘ can start if next works. Draw line across.
start '-' (E,W) "-+" draw "M {RO} L {O}";
start '|' (N,S) "|+" draw "M {RO} L {O}";
start '+' ANY ANY draw "M {C}";
# ‘.‘ and ‘’‘ make rounded corners. Draw curve through center.
step ANY (E,NE,N,NW,W) '.' (E,SE,S,SW,W) "-|\/" draw "Q {C} {O}";
step ANY (E,SE,S,SW,W) "'" (E,NE,N,NW,W) "-|\/" draw "Q {C} {O}";
# ... for a loop, draw curve from incoming edge to outgoing one.
# loop ANY (E,NE,N,NW,W) '.' (E,SE,S,SW,W) "-|\/" draw "M {I} Q {C} {O}";
# loop ANY (E,SE,S,SW,W) ''' (E,NE,N,NW,W) "-|\/" draw "M {I} Q {C} {O}";
# `-` and `|` connect w/ most things. Draw line to outgoing edge.
# step "+-.'" (E, W) '-' (maybe (E, W) "-+.'>") draw "L {O}";
# step "+|.'" (N, S) '|' (maybe (N, S) "|+.'" ) draw "L {O}";
# `+` is a corner; ensure compatible. Just draw line to center
# (the rest of corner is handled by next character, if present).
# step "|-/\>" ANY '+' (maybe (N,S) "|") draw "L {C}";
# step "|-/\>" ANY '+' (maybe (E,W) "-") draw "L {C}";
# step "|-/\>" ANY '+' (NE,SW) "/") draw "L {C}";
# step "|-/\>" ANY '+' (NW,SE) "\") draw "L {C}";
# `/`, `\` are diagonals. Draw line to outgoing corner.
# step ANY (NE, SW) '/' (maybe (NE, SW) "/+.'") draw "L {O}";
# step ANY (NW, SE) '\' (maybe (NW, SE) "\+.'") draw "L {O}";
# Special case arrowhead code (1st does not touch; 2nd + 3rd do)
# end '-' E '>' draw "L {C} l 3,0 m -3,-3 l 3,3 l -3,3 m 0,-3";
# step '-' E '>' E '+' draw "L {E} m -2,0 l 4,0 m -4,-3 l 4,3 l -4,3 m 0,-3 m 4,0";
# step '+' W '>' W '-' draw "M {E} m -2,0 l 4,0 m -4,-3 l 4,3 l -4,3 m 0,-3 m 4,0 M {E} L {C}";
"#;
#[test]
fn sanity_check_1() {
assert_ok!(parse_rules(r#"loop "|-/\" ANY "+" (N,S) "|" draw "M {C}"; "#));
assert_ok!(parse_rules(SAMPLE_GRAMMAR));
}
#[test]
fn are_attributes_supported() {
assert_ok!(parse_rules(r#"
start '^' (S) ':' draw "M {C} l 0,-5 m -3,5 l 3,-5 l 3, 5 m -3,0" attrs [("stroke-dasharray", "5,2")];
"#));
}