|
1 use std::io; |
|
2 use std::io::BufWriter; |
|
3 use std::io::Write; |
|
4 |
|
5 #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
|
6 enum Element { |
|
7 Added, |
|
8 Deleted, |
|
9 Replaced, |
|
10 Other, |
|
11 Comment, |
|
12 } |
|
13 |
|
14 #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
|
15 enum Status { |
|
16 Output(Element), |
|
17 Ignore(Element), |
|
18 Scan(Element, bool), |
|
19 } |
|
20 |
|
21 fn main() { |
|
22 let input = io::stdin(); |
|
23 let mut output = BufWriter::new(io::stdout()); |
|
24 let mut status_stack = Vec::new(); |
|
25 |
|
26 use Status::*; |
|
27 use Element::*; |
|
28 |
|
29 let current = |s : &Vec<Status>| s.last().map_or(Output(Other), |s| *s); |
|
30 let mut out = |c : char| { write!(output, "{}", c).unwrap(); }; |
|
31 let mut lineno = 0; |
|
32 |
|
33 for l in input.lines().map(|l| l.unwrap()) { |
|
34 lineno += 1; |
|
35 let mut chars = l.chars(); |
|
36 let started_ignore = if let Ignore(_) = current(&status_stack) { true } else { false }; |
|
37 let mut maybe_next_char = None; |
|
38 'process_line: loop { |
|
39 let next_char = match maybe_next_char { |
|
40 None => chars.next(), |
|
41 Some(c) => { |
|
42 maybe_next_char = None; |
|
43 Some(c) |
|
44 } |
|
45 }; |
|
46 match(current(&status_stack), next_char) { |
|
47 (_, None) => { |
|
48 break 'process_line; |
|
49 }, |
|
50 (st @ (Output(e) | Ignore(e)), Some('\\')) if e != Comment => { |
|
51 let mut command = String::new(); |
|
52 let mut first = true; |
|
53 maybe_next_char = 'scan_command: loop { |
|
54 match chars.next() { |
|
55 Some(c) if first && (c=='{' || c=='}' || c=='\\') => { |
|
56 command.push(c); |
|
57 break 'scan_command None; |
|
58 }, |
|
59 Some(c) if c.is_alphanumeric() => { |
|
60 command.push(c); |
|
61 }, |
|
62 maybe_c => { |
|
63 break 'scan_command maybe_c; |
|
64 } |
|
65 } |
|
66 first = false; |
|
67 }; |
|
68 let output_guard = if let Ignore(_) = st { false } else { true }; |
|
69 match command.as_str() { |
|
70 "added" => { |
|
71 status_stack.push(Scan(Added, true && output_guard)); |
|
72 }, |
|
73 "replaced" => { |
|
74 status_stack.push(Scan(Replaced, true && output_guard)); |
|
75 }, |
|
76 "deleted" => { |
|
77 status_stack.push(Scan(Deleted, false)); |
|
78 }, |
|
79 _ => { |
|
80 if output_guard { |
|
81 out('\\'); |
|
82 command.chars().for_each(|c| out(c.clone())); |
|
83 } |
|
84 } |
|
85 }; |
|
86 }, |
|
87 (Scan(next, o), Some(c)) => { |
|
88 match c { |
|
89 '{' => { |
|
90 status_stack.pop(); |
|
91 status_stack.push(if o { Output(next) } else { Ignore(next) }); |
|
92 }, |
|
93 ' ' => { |
|
94 }, |
|
95 _ => panic!("Non-whitespace character ({c}) separating arguments on\ |
|
96 line {lineno}"), |
|
97 } |
|
98 }, |
|
99 (Output(e), Some('{')) if e != Comment => { |
|
100 out('{'); |
|
101 status_stack.push(Output(Other)); |
|
102 }, |
|
103 (Ignore(e), Some('{')) if e != Comment => { |
|
104 status_stack.push(Ignore(Other)); |
|
105 }, |
|
106 (Output(Added) | Ignore(Added) | Output(Deleted) | Ignore(Deleted), Some('}')) => { |
|
107 status_stack.pop(); |
|
108 }, |
|
109 (Output(Replaced) | Ignore(Replaced), Some('}')) => { |
|
110 status_stack.pop(); |
|
111 status_stack.push(Scan(Deleted, false)); |
|
112 }, |
|
113 (Output(Other), Some('}')) => { |
|
114 out('}'); |
|
115 status_stack.pop(); |
|
116 }, |
|
117 (Ignore(_), Some('}')) => { |
|
118 status_stack.pop(); |
|
119 }, |
|
120 (Output(e), Some('%')) if e != Comment=> { |
|
121 out('%'); |
|
122 status_stack.push(Output(Comment)); |
|
123 }, |
|
124 (Ignore(e), Some('%')) if e != Comment => { |
|
125 status_stack.push(Ignore(Comment)); |
|
126 }, |
|
127 (Output(_), Some(c)) => { |
|
128 out(c); |
|
129 }, |
|
130 (Ignore(_), Some(_)) => { |
|
131 }, |
|
132 }; |
|
133 } |
|
134 match current(&status_stack) { |
|
135 Ignore(e) => { |
|
136 if !started_ignore { |
|
137 out('\n'); |
|
138 } |
|
139 if e == Comment { |
|
140 status_stack.pop(); |
|
141 } |
|
142 }, |
|
143 Output(e) => { |
|
144 out('\n'); |
|
145 if e == Comment { |
|
146 status_stack.pop(); |
|
147 } |
|
148 }, |
|
149 Scan(_, _) => { |
|
150 }, |
|
151 } |
|
152 } |
|
153 |
|
154 output.flush().unwrap(); |
|
155 } |