1use std::fmt;
4
5use smallvec::SmallVec;
6use uuid::UUID;
7
8use crate::Atom;
9
10#[derive(Clone, Copy, PartialEq, Eq)]
12pub enum Terminator {
13 Raw,
15 Query,
17 Header,
19 Reduced,
21}
22
23impl fmt::Debug for Terminator {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 write!(f, "{}", self)
26 }
27}
28
29impl fmt::Display for Terminator {
30 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31 match self {
32 Terminator::Raw => f.write_str(";"),
33 Terminator::Reduced => f.write_str(","),
34 Terminator::Header => f.write_str("!"),
35 Terminator::Query => f.write_str("?"),
36 }
37 }
38}
39
40impl Default for Terminator {
41 fn default() -> Terminator {
42 Terminator::Reduced
43 }
44}
45
46impl Terminator {
47 pub fn from_string(inp: &str) -> Result<Terminator, &'static str> {
49 match &inp {
50 &";" => Ok(Terminator::Raw),
51 &"?" => Ok(Terminator::Query),
52 &"!" => Ok(Terminator::Header),
53 &"," => Ok(Terminator::Reduced),
54 _ => Err("invalid terminator"),
55 }
56 }
57
58 pub fn from_char(inp: char) -> Result<Terminator, &'static str> {
60 Terminator::from_string(&inp.to_string())
61 }
62}
63
64#[derive(Clone, PartialEq)]
71pub struct Op {
72 pub ty: UUID,
74 pub object: UUID,
76 pub event: UUID,
78 pub location: UUID,
80 pub atoms: SmallVec<[Atom; 3]>,
82 pub term: Terminator,
84}
85
86impl Op {
87 pub fn parse_inplace<'a>(
90 prev: &mut Option<Op>, input: &'a str,
91 ) -> Option<&'a str> {
92 let mut ret = input;
93 let mut ty = None;
94 let mut event = None;
95 let mut object = None;
96 let mut location = None;
97
98 {
99 let mut prev_uu = prev.as_ref().map(|p| &p.location);
100
101 ret = ret.trim_start();
103 if ret.starts_with('*') {
104 let ctx = prev.as_ref().map(|p| (&p.ty, prev_uu.unwrap()));
105
106 match UUID::parse(&ret[1..], ctx) {
107 Some((t, rest)) => {
108 ret = rest;
109 ty = Some(t);
110 prev_uu = ty.as_ref();
111 }
112 None => {
113 ret = &ret[1..];
114 ty = prev.as_ref().map(|p| p.ty.clone());
115 prev_uu = prev.as_ref().map(|p| &p.ty);
116 }
117 }
118 } else {
119 prev_uu = prev.as_ref().map(|p| &p.ty);
120 }
121
122 ret = ret.trim_start();
124 if ret.starts_with('#') {
125 let ctx = prev.as_ref().map(|p| (&p.object, prev_uu.unwrap()));
126
127 match UUID::parse(&ret[1..], ctx) {
128 Some((obj, rest)) => {
129 ret = rest;
130 object = Some(obj);
131 prev_uu = object.as_ref();
132 }
133 None => {
134 ret = &ret[1..];
135 object = prev.as_ref().map(|p| p.object.clone());
136 prev_uu = prev.as_ref().map(|p| &p.object);
137 }
138 }
139 } else {
140 prev_uu = prev.as_ref().map(|p| &p.object);
141 }
142
143 ret = ret.trim_start();
145 if ret.starts_with('@') {
146 let ctx = prev.as_ref().map(|p| (&p.event, prev_uu.unwrap()));
147
148 match UUID::parse(&ret[1..], ctx) {
149 Some((ev, rest)) => {
150 ret = rest;
151 event = Some(ev);
152 prev_uu = event.as_ref();
153 }
154 None => {
155 ret = &ret[1..];
156 event = prev.as_ref().map(|p| p.event.clone());
157 prev_uu = prev.as_ref().map(|p| &p.event);
158 }
159 }
160 } else {
161 prev_uu = prev.as_ref().map(|p| &p.event);
162 }
163
164 ret = ret.trim_start();
166 if ret.starts_with(':') {
167 let ctx =
168 prev.as_ref().map(|p| (&p.location, prev_uu.unwrap()));
169
170 match UUID::parse(&ret[1..], ctx) {
171 Some((loc, rest)) => {
172 ret = rest;
173 location = Some(loc);
174 }
175 None => {
176 ret = &ret[1..];
177 location = prev.as_ref().map(|p| p.location.clone());
178 }
179 }
180 }
181 }
182
183 let no_spec = ty.is_none()
184 && event.is_none()
185 && object.is_none()
186 && location.is_none();
187
188 let prev = match prev {
189 &mut Some(ref mut prev) => {
190 if let Some(ty) = ty {
191 prev.ty = ty;
192 }
193 if let Some(event) = event {
194 prev.event = event;
195 }
196 if let Some(object) = object {
197 prev.object = object;
198 }
199 if let Some(location) = location {
200 prev.location = location;
201 }
202
203 prev
204 }
205 &mut None
206 if ty.is_some() && object.is_some() && event.is_some() =>
207 {
208 *prev = Some(Op {
209 ty: ty.unwrap(),
210 object: object.unwrap(),
211 event: event.unwrap(),
212 location: location.unwrap_or_else(UUID::zero),
213 atoms: Default::default(),
214 term: Terminator::Header,
215 });
216 prev.as_mut().unwrap()
217 }
218 &mut None => {
219 return None;
220 }
221 };
222
223 prev.atoms.clear();
225
226 {
227 let mut prev_uu = prev.object.clone();
228
229 while let Some((atm, rest)) =
230 Atom::parse(ret, Some((&prev.object, &prev_uu)))
231 {
232 match &atm {
233 &Atom::UUID(ref uu) => {
234 prev_uu = uu.clone();
235 }
236 _ => {}
237 }
238
239 ret = rest;
240 prev.atoms.push(atm);
241 }
242 }
243
244 ret = ret.trim_start();
246 match ret.chars().next() {
247 Some('!') => {
248 prev.term = Terminator::Header;
249 ret = &ret[1..];
250 }
251 Some('?') => {
252 prev.term = Terminator::Query;
253 ret = &ret[1..];
254 }
255 Some(',') => {
256 prev.term = Terminator::Reduced;
257 ret = &ret[1..];
258 }
259 Some(';') => {
260 prev.term = Terminator::Raw;
261 ret = &ret[1..];
262 }
263 _ if !no_spec || !prev.atoms.is_empty() => {
264 prev.term = Terminator::Reduced;
265 }
266 _ => {
267 return None;
268 }
269 }
270
271 Some(ret)
272 }
273
274 pub fn compress(&self, context: Option<&Op>) -> String {
276 let mut ret = String::default();
277
278 if context.map(|o| o.ty != self.ty).unwrap_or(true) {
279 let ctx = context.map(|p| (&p.ty, &p.ty));
280
281 ret += "*";
282 ret += &self.ty.compress(ctx);
283 }
284
285 if context.map(|o| o.object != self.object).unwrap_or(true) {
286 let ctx = context.map(|p| (&p.object, &p.ty));
287
288 ret += "#";
289 ret += &self.object.compress(ctx);
290 }
291
292 if context.map(|o| o.event != self.event).unwrap_or(true) {
293 let ctx = context.map(|p| (&p.event, &p.object));
294
295 ret += "@";
296 ret += &self.event.compress(ctx);
297 }
298
299 if context.map(|o| o.location != self.location).unwrap_or(true) {
300 let ctx = context.map(|p| (&p.location, &p.event));
301
302 ret += ":";
303 ret += &self.location.compress(ctx);
304 }
305
306 if ret.is_empty() {
307 ret = "@".to_string();
308 }
309
310 let mut prev = &self.object;
311 for atm in self.atoms.iter() {
312 match atm {
313 &Atom::Integer(i) => {
314 ret += &format!("={}", i);
315 }
316 &Atom::String(ref i) => {
317 ret += &format!("'{}'", i);
318 }
319 &Atom::Float(i) => {
320 ret += &format!("^{}", i);
321 }
322 &Atom::UUID(ref i) => {
323 ret += ">";
324 ret += &i.compress(Some((prev, prev)));
325 prev = i;
326 }
327 }
328 }
329
330 ret + &format!("{}", self.term)
331 }
332}
333impl fmt::Debug for Op {
334 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335 write!(f, "{}", self)
336 }
337}
338
339impl fmt::Display for Op {
340 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341 write!(
342 f,
343 "*{}#{}@{}:{}",
344 self.ty, self.object, self.event, self.location
345 )?;
346
347 match self.atoms.len() {
348 0 => write!(f, "{}", self.term),
349 1 => write!(f, "{}{}", self.atoms[0], self.term),
350 _ => {
351 write!(f, "{}", self.atoms[0])?;
352 for atm in self.atoms[1..].iter() {
353 write!(f, ", {}", atm)?;
354 }
355 write!(f, "{}", self.term)
356 }
357 }
358 }
359}
360
361#[test]
362fn compress_roundtrip() {
363 use crate::Frame;
364
365 let f = Frame::parse(
366 "
367 *set#mice@1YKDXO3201+1YKDXO!
368 @>mouse$1YKDXO
369 @(WBF901(WBY>mouse$1YKDWBY
370 @[67H01[6>mouse$1YKDW6
371 @(Uh4j01(Uh>mouse$1YKDUh
372 @(S67V01(S6>mouse$1YKDS6
373 @(Of(N3:1YKDN3DS01+1YKDN3,
374 @(MvBV01(IuJ:0>mouse$1YKDIuJ
375 @(LF:1YKDIuEY01+1YKDIuJ,
376 :{A601,
377 @(Io5l01[oA:0>mouse$1YKDIoA
378 @[l7_01[l>mouse$1YKDIl
379 @(57(4B:1YKD4B3f01+1YKD4B,
380 @(0bB401+1YKCsd:0>mouse$1Y",
381 );
382
383 let ops = f.into_iter().collect::<Vec<_>>();
384 for win in ops.windows(2) {
385 let ctx = &win[0];
386 let op = &win[1];
387 let txt = op.compress(Some(ctx));
388 let mut back = Some(ctx.clone());
389
390 Op::parse_inplace(&mut back, &txt[..]);
391
392 eprintln!("ctx: {}", ctx);
393 eprintln!("op : {}", op);
394 eprintln!("txt: {}", txt);
395 eprintln!("rt : {}", back.as_ref().unwrap());
396 eprintln!("");
397 assert_eq!(back.unwrap(), *op);
398 }
399}