boxworks_lang/
convert.rs

1//! Conversions between Box language and Boxworks data structures
2
3use std::borrow::Cow;
4
5use crate::ast;
6use boxworks::ds;
7
8/// Convert a Boxworks data structure to a Box language data structure.
9pub trait ToBoxLang {
10    type Output;
11    fn to_box_lang(&self) -> Self::Output;
12}
13
14/// Convert a Box language data structure to a Boxworks data structure.
15pub trait ToBoxworks {
16    type Output;
17    fn to_boxworks(&self) -> Self::Output;
18}
19
20impl ToBoxLang for ds::Vertical {
21    type Output = ast::Vertical<'static>;
22    fn to_box_lang(&self) -> Self::Output {
23        use boxworks::ds::Vertical::*;
24        match self {
25            HList(hlist) => ast::Vertical::Hlist(hlist.to_box_lang()),
26            VList(vlist) => ast::Vertical::Vlist(vlist.to_box_lang()),
27            Rule(rule) => ast::Vertical::Rule(rule.to_box_lang()),
28            Mark(mark) => ast::Vertical::Mark(mark.to_box_lang()),
29            Insertion(insertion) => ast::Vertical::Insertion(insertion.to_box_lang()),
30            Whatsit(_whatsit) => todo!(),
31            Math(math) => ast::Vertical::Math(math.to_box_lang()),
32            Glue(glue) => ast::Vertical::Glue(glue.to_box_lang()),
33            Kern(kern) => ast::Vertical::Kern(kern.to_box_lang()),
34            Penalty(penalty) => ast::Vertical::Penalty(penalty.to_box_lang()),
35        }
36    }
37}
38
39impl<'a> ToBoxworks for ast::Vertical<'a> {
40    type Output = ds::Vertical;
41
42    fn to_boxworks(&self) -> Self::Output {
43        use ast::Vertical::*;
44        match self {
45            Hlist(hlist_args) => ds::Vertical::HList(hlist_args.to_boxworks()),
46            Vlist(vlist_args) => ds::Vertical::VList(vlist_args.to_boxworks()),
47            Glue(glue_args) => ds::Vertical::Glue(glue_args.to_boxworks()),
48            Kern(kern_args) => ds::Vertical::Kern(kern_args.to_boxworks()),
49            Penalty(penalty_args) => ds::Vertical::Penalty(penalty_args.to_boxworks()),
50            Rule(rule_args) => ds::Vertical::Rule(rule_args.to_boxworks()),
51            Mark(mark_args) => ds::Vertical::Mark(mark_args.to_boxworks()),
52            Insertion(insertion_args) => ds::Vertical::Insertion(insertion_args.to_boxworks()),
53            Math(math_args) => ds::Vertical::Math(math_args.to_boxworks()),
54        }
55    }
56}
57
58impl ToBoxLang for Vec<ds::Horizontal> {
59    type Output = Vec<ast::Horizontal<'static>>;
60    fn to_box_lang(&self) -> Self::Output {
61        let mut out = vec![];
62        let mut current_font: Option<u32> = None;
63        let mut buf: String = Default::default();
64        let flush_chars = |out: &mut Vec<ast::Horizontal<'static>>,
65                           current_font: &mut Option<u32>,
66                           buf: &mut String| {
67            let Some(current_font) = current_font.take() else {
68                // Nothing to flush.
69                return;
70            };
71            let current_font: i32 = current_font.try_into().unwrap();
72            out.push(ast::Horizontal::Chars(ast::Chars {
73                content: Cow::<str>::Owned(buf.clone()).into(),
74                font: current_font.into(),
75            }));
76            buf.clear();
77        };
78        for elem in self {
79            match elem {
80                ds::Horizontal::Char(ds::Char { char, font }) => {
81                    if Some(*font) != current_font {
82                        flush_chars(&mut out, &mut current_font, &mut buf);
83                    }
84                    current_font = Some(*font);
85                    buf.push(*char);
86                }
87                _ => {
88                    flush_chars(&mut out, &mut current_font, &mut buf);
89                    out.push(elem.to_box_lang());
90                }
91            }
92        }
93        flush_chars(&mut out, &mut current_font, &mut buf);
94        out
95    }
96}
97
98impl ToBoxLang for Vec<ds::Vertical> {
99    type Output = Vec<ast::Vertical<'static>>;
100    fn to_box_lang(&self) -> Self::Output {
101        self.iter().map(|b| b.to_box_lang()).collect()
102    }
103}
104
105impl ToBoxLang for Vec<ds::DiscretionaryElem> {
106    type Output = Vec<ast::DiscretionaryElem<'static>>;
107    fn to_box_lang(&self) -> Self::Output {
108        self.iter().map(|b| b.to_box_lang()).collect()
109    }
110}
111
112impl<'a> ToBoxworks for Vec<ast::Horizontal<'a>> {
113    type Output = Vec<ds::Horizontal>;
114    fn to_boxworks(&self) -> Self::Output {
115        self.iter().flat_map(|b| b.to_boxworks()).collect()
116    }
117}
118
119impl<'a> ToBoxworks for Vec<ast::Vertical<'a>> {
120    type Output = Vec<ds::Vertical>;
121    fn to_boxworks(&self) -> Self::Output {
122        self.iter().map(|b| b.to_boxworks()).collect()
123    }
124}
125
126impl<'a> ToBoxworks for Vec<ast::DiscretionaryElem<'a>> {
127    type Output = Vec<ds::DiscretionaryElem>;
128    fn to_boxworks(&self) -> Self::Output {
129        self.iter().flat_map(|b| b.to_boxworks()).collect()
130    }
131}
132
133impl<'a> ToBoxworks for ast::Horizontal<'a> {
134    type Output = Vec<ds::Horizontal>;
135
136    fn to_boxworks(&self) -> Self::Output {
137        use ast::Horizontal::*;
138        match self {
139            Chars(chars_args) => chars_args.to_boxworks(),
140            Glue(glue_args) => vec![ds::Horizontal::Glue(glue_args.to_boxworks())],
141            Kern(kern_args) => vec![ds::Horizontal::Kern(kern_args.to_boxworks())],
142            Hlist(hlist_args) => vec![ds::Horizontal::HList(hlist_args.to_boxworks())],
143            Vlist(vlist_args) => vec![ds::Horizontal::VList(vlist_args.to_boxworks())],
144            Ligature(lig_args) => vec![ds::Horizontal::Ligature(lig_args.to_boxworks())],
145            Discretionary(disc_args) => {
146                vec![ds::Horizontal::Discretionary(disc_args.to_boxworks())]
147            }
148            Rule(rule_args) => vec![ds::Horizontal::Rule(rule_args.to_boxworks())],
149            Penalty(penalty_args) => vec![ds::Horizontal::Penalty(penalty_args.to_boxworks())],
150            Mark(mark_args) => vec![ds::Horizontal::Mark(mark_args.to_boxworks())],
151            Adjust(adjust_args) => vec![ds::Horizontal::Adjust(adjust_args.to_boxworks())],
152            Insertion(insertion_args) => {
153                vec![ds::Horizontal::Insertion(insertion_args.to_boxworks())]
154            }
155            Math(math_args) => vec![ds::Horizontal::Math(math_args.to_boxworks())],
156        }
157    }
158}
159
160impl ToBoxLang for ds::Horizontal {
161    type Output = ast::Horizontal<'static>;
162    fn to_box_lang(&self) -> Self::Output {
163        use boxworks::ds::Horizontal::*;
164        match self {
165            Char(char) => ast::Horizontal::Chars(char.to_box_lang()),
166            HList(hlist) => ast::Horizontal::Hlist(hlist.to_box_lang()),
167            VList(vlist) => ast::Horizontal::Vlist(vlist.to_box_lang()),
168            Rule(rule) => ast::Horizontal::Rule(rule.to_box_lang()),
169            Mark(mark) => ast::Horizontal::Mark(mark.to_box_lang()),
170            Insertion(insertion) => ast::Horizontal::Insertion(insertion.to_box_lang()),
171            Adjust(adjust) => ast::Horizontal::Adjust(adjust.to_box_lang()),
172            Ligature(ligature) => ast::Horizontal::Ligature(ligature.to_box_lang()),
173            Discretionary(discretionary) => {
174                ast::Horizontal::Discretionary(discretionary.to_box_lang())
175            }
176            Whatsit(_whatsit) => todo!(),
177            Math(math) => ast::Horizontal::Math(math.to_box_lang()),
178            Glue(glue) => ast::Horizontal::Glue(glue.to_box_lang()),
179            Kern(kern) => ast::Horizontal::Kern(kern.to_box_lang()),
180            Penalty(penalty) => ast::Horizontal::Penalty(penalty.to_box_lang()),
181        }
182    }
183}
184
185impl<'a> ToBoxworks for ast::DiscretionaryElem<'a> {
186    type Output = Vec<ds::DiscretionaryElem>;
187
188    fn to_boxworks(&self) -> Self::Output {
189        use ast::DiscretionaryElem::*;
190        use ds::DiscretionaryElem as Out;
191        match self {
192            Chars(chars_args) => chars_to_discretionary_elems(chars_args),
193            Kern(kern_args) => vec![Out::Kern(kern_args.to_boxworks())],
194            Hlist(hlist_args) => vec![Out::HList(hlist_args.to_boxworks())],
195            Vlist(vlist_args) => vec![Out::VList(vlist_args.to_boxworks())],
196            Ligature(lig_args) => vec![Out::Ligature(lig_args.to_boxworks())],
197            Rule(rule_args) => vec![Out::Rule(rule_args.to_boxworks())],
198        }
199    }
200}
201
202impl ToBoxLang for ds::DiscretionaryElem {
203    type Output = ast::DiscretionaryElem<'static>;
204    fn to_box_lang(&self) -> Self::Output {
205        use ast::DiscretionaryElem as Out;
206        use boxworks::ds::DiscretionaryElem::*;
207        match self {
208            Char(char) => Out::Chars(char.to_box_lang()),
209            HList(hlist) => Out::Hlist(hlist.to_box_lang()),
210            VList(vlist) => Out::Vlist(vlist.to_box_lang()),
211            Rule(rule) => Out::Rule(rule.to_box_lang()),
212            Ligature(ligature) => Out::Ligature(ligature.to_box_lang()),
213            Kern(kern) => Out::Kern(kern.to_box_lang()),
214        }
215    }
216}
217
218impl ToBoxLang for ds::VList {
219    type Output = ast::Vlist<'static>;
220    fn to_box_lang(&self) -> Self::Output {
221        ast::Vlist {
222            content: self.list.to_box_lang().into(),
223        }
224    }
225}
226
227impl ToBoxLang for ds::HList {
228    type Output = ast::Hlist<'static>;
229    fn to_box_lang(&self) -> Self::Output {
230        ast::Hlist {
231            width: self.width.into(),
232            content: self.list.to_box_lang().into(),
233        }
234    }
235}
236
237impl<'a> ToBoxworks for ast::Hlist<'a> {
238    type Output = ds::HList;
239    fn to_boxworks(&self) -> Self::Output {
240        ds::HList {
241            width: self.width.value,
242            list: self.content.value.to_boxworks(),
243            // TODO: all the other stuff
244            ..Default::default()
245        }
246    }
247}
248
249impl<'a> ToBoxworks for ast::Vlist<'a> {
250    type Output = ds::VList;
251    fn to_boxworks(&self) -> Self::Output {
252        ds::VList {
253            list: self.content.value.to_boxworks(),
254            // TODO: all the other stuff
255            ..Default::default()
256        }
257    }
258}
259
260impl<'a> ToBoxworks for ast::Ligature<'a> {
261    type Output = ds::Ligature;
262
263    fn to_boxworks(&self) -> Self::Output {
264        ds::Ligature {
265            included_left_boundary: false,  // TODO
266            included_right_boundary: false, // TODO,
267            char: self.char.value,
268            font: self.font.value as u32,
269            original_chars: self.original_chars.value.clone().into(),
270        }
271    }
272}
273
274impl ToBoxLang for ds::Ligature {
275    type Output = ast::Ligature<'static>;
276
277    fn to_box_lang(&self) -> Self::Output {
278        ast::Ligature {
279            char: self.char.into(),
280            original_chars: Cow::<'static, str>::Owned(format!["{}", self.original_chars]).into(),
281            font: (self.font as i32).into(),
282        }
283    }
284}
285
286impl ToBoxLang for ds::Char {
287    type Output = ast::Chars<'static>;
288    fn to_box_lang(&self) -> Self::Output {
289        ast::Chars {
290            content: Cow::<'static, str>::Owned(format!["{}", self.char]).into(),
291            font: (self.font as i32).into(),
292        }
293    }
294}
295
296impl<'a> ToBoxworks for ast::Chars<'a> {
297    type Output = Vec<ds::Horizontal>;
298    fn to_boxworks(&self) -> Self::Output {
299        self.content
300            .value
301            .chars()
302            .map(|c| {
303                ds::Horizontal::Char(ds::Char {
304                    char: c,
305                    font: self.font.value as u32,
306                })
307            })
308            .collect()
309    }
310}
311
312fn chars_to_discretionary_elems<'a>(chars: &ast::Chars<'a>) -> Vec<ds::DiscretionaryElem> {
313    chars
314        .content
315        .value
316        .chars()
317        .map(|c| {
318            ds::DiscretionaryElem::Char(ds::Char {
319                char: c,
320                font: chars.font.value as u32,
321            })
322        })
323        .collect()
324}
325
326impl ToBoxLang for ds::Penalty {
327    type Output = ast::Penalty<'static>;
328    fn to_box_lang(&self) -> Self::Output {
329        ast::Penalty {
330            value: self.0.into(),
331        }
332    }
333}
334
335impl<'a> ToBoxworks for ast::Penalty<'a> {
336    type Output = ds::Penalty;
337    fn to_boxworks(&self) -> Self::Output {
338        ds::Penalty(self.value.value)
339    }
340}
341
342impl ToBoxLang for ds::Glue {
343    type Output = ast::Glue<'static>;
344    fn to_box_lang(&self) -> Self::Output {
345        ast::Glue {
346            width: self.value.width.into(),
347            stretch: (self.value.stretch, self.value.stretch_order).into(),
348            shrink: (self.value.shrink, self.value.shrink_order).into(),
349        }
350    }
351}
352
353impl<'a> ToBoxworks for ast::Glue<'a> {
354    type Output = ds::Glue;
355    fn to_boxworks(&self) -> Self::Output {
356        ds::Glue {
357            value: common::Glue {
358                width: self.width.value,
359                stretch: self.stretch.value.0,
360                stretch_order: self.stretch.value.1,
361                shrink: self.shrink.value.0,
362                shrink_order: self.shrink.value.1,
363            },
364            kind: ds::GlueKind::Normal,
365        }
366    }
367}
368
369impl ToBoxLang for ds::Kern {
370    type Output = ast::Kern<'static>;
371    fn to_box_lang(&self) -> Self::Output {
372        ast::Kern {
373            width: self.width.into(),
374        }
375    }
376}
377
378impl<'a> ToBoxworks for ast::Kern<'a> {
379    type Output = ds::Kern;
380    fn to_boxworks(&self) -> Self::Output {
381        ds::Kern {
382            width: self.width.value,
383            kind: ds::KernKind::Normal,
384        }
385    }
386}
387
388impl ToBoxLang for ds::Discretionary {
389    type Output = ast::Discretionary<'static>;
390    fn to_box_lang(&self) -> Self::Output {
391        ast::Discretionary {
392            pre_break: self.pre_break.to_box_lang().into(),
393            post_break: self.post_break.to_box_lang().into(),
394            replace_count: (self.replace_count as i32).into(),
395        }
396    }
397}
398
399impl<'a> ToBoxworks for ast::Discretionary<'a> {
400    type Output = ds::Discretionary;
401    fn to_boxworks(&self) -> Self::Output {
402        ds::Discretionary {
403            pre_break: self.pre_break.value.to_boxworks(),
404            post_break: self.post_break.value.to_boxworks(),
405            replace_count: self.replace_count.value as u32,
406        }
407    }
408}
409
410impl ToBoxLang for ds::Rule {
411    type Output = ast::Rule<'static>;
412    fn to_box_lang(&self) -> Self::Output {
413        ast::Rule {
414            height: self.height.into(),
415            width: self.width.into(),
416            depth: self.depth.into(),
417        }
418    }
419}
420
421impl<'a> ToBoxworks for ast::Rule<'a> {
422    type Output = ds::Rule;
423    fn to_boxworks(&self) -> Self::Output {
424        ds::Rule {
425            height: self.height.value,
426            width: self.width.value,
427            depth: self.depth.value,
428        }
429    }
430}
431
432impl ToBoxLang for ds::Mark {
433    type Output = ast::Mark<'static>;
434    fn to_box_lang(&self) -> Self::Output {
435        ast::Mark::default()
436    }
437}
438
439impl<'a> ToBoxworks for ast::Mark<'a> {
440    type Output = ds::Mark;
441    fn to_boxworks(&self) -> Self::Output {
442        ds::Mark { list: vec![] }
443    }
444}
445
446impl ToBoxLang for ds::Adjust {
447    type Output = ast::Adjust<'static>;
448    fn to_box_lang(&self) -> Self::Output {
449        ast::Adjust {
450            content: self.list.to_box_lang().into(),
451        }
452    }
453}
454
455impl<'a> ToBoxworks for ast::Adjust<'a> {
456    type Output = ds::Adjust;
457    fn to_boxworks(&self) -> Self::Output {
458        ds::Adjust {
459            list: self.content.value.to_boxworks(),
460        }
461    }
462}
463
464impl ToBoxLang for ds::Insertion {
465    type Output = ast::Insertion<'static>;
466    fn to_box_lang(&self) -> Self::Output {
467        ast::Insertion {
468            box_number: (self.box_number as i32).into(),
469            height: self.height.into(),
470            split_max_depth: self.split_max_depth.into(),
471            split_top_skip_width: self.split_top_skip.width.into(),
472            split_top_skip_stretch: (
473                self.split_top_skip.stretch,
474                self.split_top_skip.stretch_order,
475            )
476                .into(),
477            split_top_skip_shrink: (self.split_top_skip.shrink, self.split_top_skip.shrink_order)
478                .into(),
479            float_penalty: (self.float_penalty as i32).into(),
480            vlist: self.vlist.to_box_lang().into(),
481        }
482    }
483}
484
485impl<'a> ToBoxworks for ast::Insertion<'a> {
486    type Output = ds::Insertion;
487    fn to_boxworks(&self) -> Self::Output {
488        ds::Insertion {
489            box_number: self.box_number.value as u8,
490            height: self.height.value,
491            split_max_depth: self.split_max_depth.value,
492            split_top_skip: common::Glue {
493                width: self.split_top_skip_width.value,
494                stretch: self.split_top_skip_stretch.value.0,
495                stretch_order: self.split_top_skip_stretch.value.1,
496                shrink: self.split_top_skip_shrink.value.0,
497                shrink_order: self.split_top_skip_shrink.value.1,
498            },
499            float_penalty: self.float_penalty.value as u32,
500            vlist: self.vlist.value.to_boxworks(),
501        }
502    }
503}
504
505impl ToBoxLang for ds::Math {
506    type Output = ast::Math<'static>;
507    fn to_box_lang(&self) -> Self::Output {
508        ast::Math {
509            kind: match self {
510                ds::Math::Before => Cow::Borrowed("before"),
511                ds::Math::After => Cow::Borrowed("after"),
512            }
513            .into(),
514        }
515    }
516}
517
518impl<'a> ToBoxworks for ast::Math<'a> {
519    type Output = ds::Math;
520    fn to_boxworks(&self) -> Self::Output {
521        match self.kind.value.as_ref() {
522            "after" => ds::Math::After,
523            _ => ds::Math::Before,
524        }
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531
532    macro_rules! tests {
533        ( $( ($name: ident, $input: expr, $want: expr,), )+ ) => {
534            $(
535                #[test]
536                fn $name() {
537                    let input: Vec<ds::Horizontal> = $input;
538                    let want: Vec<ast::Horizontal<'static>> = $want;
539                    let got = input.to_box_lang();
540                    assert_eq!(got, want);
541                }
542            )+
543        };
544    }
545
546    tests!(
547        (
548            chars_same_font,
549            vec![
550                ds::Char { char: 'B', font: 0 }.into(),
551                ds::Char { char: 'o', font: 0 }.into(),
552                ds::Char { char: 'x', font: 0 }.into(),
553            ],
554            vec![ast::Chars {
555                content: Cow::Borrowed("Box").into(),
556                font: 0_i32.into(),
557            }
558            .into()],
559        ),
560        (
561            chars_different_font,
562            vec![
563                ds::Char { char: 'B', font: 0 }.into(),
564                ds::Char { char: 'o', font: 0 }.into(),
565                ds::Char { char: 'x', font: 0 }.into(),
566                ds::Char { char: 'e', font: 1 }.into(),
567                ds::Char { char: 'd', font: 1 }.into(),
568            ],
569            vec![
570                ast::Chars {
571                    content: Cow::Borrowed("Box").into(),
572                    font: 0_i32.into(),
573                }
574                .into(),
575                ast::Chars {
576                    content: Cow::Borrowed("ed").into(),
577                    font: 1_i32.into(),
578                }
579                .into(),
580            ],
581        ),
582    );
583}