boxworks/lang/
convert.rs

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