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<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            HBox(hbox_args) => vec![ds::Horizontal::HBox(hbox_args.to_boxworks())],
143            VBox(vbox_args) => vec![ds::Horizontal::VBox(vbox_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 crate::ds::Horizontal::*;
164        match self {
165            Char(char) => ast::Horizontal::Chars(char.to_box_lang()),
166            HBox(hbox) => ast::Horizontal::HBox(hbox.to_box_lang()),
167            VBox(vbox) => ast::Horizontal::VBox(vbox.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            HBox(hbox_args) => vec![Out::HBox(hbox_args.to_boxworks())],
195            VBox(vbox_args) => vec![Out::VBox(vbox_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 crate::ds::DiscretionaryElem::*;
206        use ast::DiscretionaryElem as Out;
207        match self {
208            Char(char) => Out::Chars(char.to_box_lang()),
209            HBox(hbox) => Out::HBox(hbox.to_box_lang()),
210            VBox(vbox) => Out::VBox(vbox.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::VBox {
219    type Output = ast::VBox<'static>;
220    fn to_box_lang(&self) -> Self::Output {
221        ast::VBox {
222            height: self.height.into(),
223            width: self.width.into(),
224            depth: self.depth.into(),
225            shift_amount: self.shift_amount.into(),
226            content: self.list.to_box_lang().into(),
227        }
228    }
229}
230
231impl ToBoxLang for ds::HBox {
232    type Output = ast::HBox<'static>;
233    fn to_box_lang(&self) -> Self::Output {
234        ast::HBox {
235            height: self.height.into(),
236            width: self.width.into(),
237            depth: self.depth.into(),
238            shift_amount: self.shift_amount.into(),
239            glue_ratio: self.glue_ratio.into(),
240            glue_order: self.glue_order.into(),
241            content: self.list.to_box_lang().into(),
242        }
243    }
244}
245
246impl<'a> ToBoxworks for ast::HBox<'a> {
247    type Output = ds::HBox;
248    fn to_boxworks(&self) -> Self::Output {
249        ds::HBox {
250            height: self.height.value,
251            width: self.width.value,
252            depth: self.depth.value,
253            shift_amount: self.shift_amount.value,
254            glue_ratio: self.glue_ratio.value,
255            glue_order: self.glue_order.value,
256            list: self.content.value.to_boxworks(),
257        }
258    }
259}
260
261impl<'a> ToBoxworks for ast::VBox<'a> {
262    type Output = ds::VBox;
263    fn to_boxworks(&self) -> Self::Output {
264        ds::VBox {
265            height: self.height.value,
266            width: self.width.value,
267            depth: self.depth.value,
268            shift_amount: self.shift_amount.value,
269            list: self.content.value.to_boxworks(),
270            ..Default::default()
271        }
272    }
273}
274
275impl<'a> ToBoxworks for ast::Ligature<'a> {
276    type Output = ds::Ligature;
277
278    fn to_boxworks(&self) -> Self::Output {
279        ds::Ligature {
280            included_left_boundary: false,  // TODO
281            included_right_boundary: false, // TODO,
282            char: self.char.value,
283            font: self.font.value as u32,
284            original_chars: self.original_chars.value.clone().into(),
285        }
286    }
287}
288
289impl ToBoxLang for ds::Ligature {
290    type Output = ast::Ligature<'static>;
291
292    fn to_box_lang(&self) -> Self::Output {
293        ast::Ligature {
294            char: self.char.into(),
295            original_chars: Cow::<'static, str>::Owned(format!["{}", self.original_chars]).into(),
296            font: (self.font as i32).into(),
297        }
298    }
299}
300
301impl ToBoxLang for ds::Char {
302    type Output = ast::Chars<'static>;
303    fn to_box_lang(&self) -> Self::Output {
304        ast::Chars {
305            content: Cow::<'static, str>::Owned(format!["{}", self.char]).into(),
306            font: (self.font as i32).into(),
307        }
308    }
309}
310
311impl<'a> ToBoxworks for ast::Chars<'a> {
312    type Output = Vec<ds::Horizontal>;
313    fn to_boxworks(&self) -> Self::Output {
314        self.content
315            .value
316            .chars()
317            .map(|c| {
318                ds::Horizontal::Char(ds::Char {
319                    char: c,
320                    font: self.font.value as u32,
321                })
322            })
323            .collect()
324    }
325}
326
327fn chars_to_discretionary_elems<'a>(chars: &ast::Chars<'a>) -> Vec<ds::DiscretionaryElem> {
328    chars
329        .content
330        .value
331        .chars()
332        .map(|c| {
333            ds::DiscretionaryElem::Char(ds::Char {
334                char: c,
335                font: chars.font.value as u32,
336            })
337        })
338        .collect()
339}
340
341impl ToBoxLang for ds::Penalty {
342    type Output = ast::Penalty<'static>;
343    fn to_box_lang(&self) -> Self::Output {
344        ast::Penalty {
345            value: self.0.into(),
346        }
347    }
348}
349
350impl<'a> ToBoxworks for ast::Penalty<'a> {
351    type Output = ds::Penalty;
352    fn to_boxworks(&self) -> Self::Output {
353        ds::Penalty(self.value.value)
354    }
355}
356
357impl ToBoxLang for ds::Glue {
358    type Output = ast::Glue<'static>;
359    fn to_box_lang(&self) -> Self::Output {
360        ast::Glue {
361            width: self.value.width.into(),
362            stretch: (self.value.stretch, self.value.stretch_order).into(),
363            shrink: (self.value.shrink, self.value.shrink_order).into(),
364        }
365    }
366}
367
368impl<'a> ToBoxworks for ast::Glue<'a> {
369    type Output = ds::Glue;
370    fn to_boxworks(&self) -> Self::Output {
371        ds::Glue {
372            value: common::Glue {
373                width: self.width.value,
374                stretch: self.stretch.value.0,
375                stretch_order: self.stretch.value.1,
376                shrink: self.shrink.value.0,
377                shrink_order: self.shrink.value.1,
378            },
379            kind: ds::GlueKind::Normal,
380        }
381    }
382}
383
384impl ToBoxLang for ds::Kern {
385    type Output = ast::Kern<'static>;
386    fn to_box_lang(&self) -> Self::Output {
387        ast::Kern {
388            width: self.width.into(),
389        }
390    }
391}
392
393impl<'a> ToBoxworks for ast::Kern<'a> {
394    type Output = ds::Kern;
395    fn to_boxworks(&self) -> Self::Output {
396        ds::Kern {
397            width: self.width.value,
398            kind: ds::KernKind::Normal,
399        }
400    }
401}
402
403impl ToBoxLang for ds::Discretionary {
404    type Output = ast::Discretionary<'static>;
405    fn to_box_lang(&self) -> Self::Output {
406        ast::Discretionary {
407            pre_break: self.pre_break.to_box_lang().into(),
408            post_break: self.post_break.to_box_lang().into(),
409            replace_count: (self.replace_count as i32).into(),
410        }
411    }
412}
413
414impl<'a> ToBoxworks for ast::Discretionary<'a> {
415    type Output = ds::Discretionary;
416    fn to_boxworks(&self) -> Self::Output {
417        ds::Discretionary {
418            pre_break: self.pre_break.value.to_boxworks(),
419            post_break: self.post_break.value.to_boxworks(),
420            replace_count: self.replace_count.value as u32,
421        }
422    }
423}
424
425impl ToBoxLang for ds::Rule {
426    type Output = ast::Rule<'static>;
427    fn to_box_lang(&self) -> Self::Output {
428        ast::Rule {
429            height: ast::MaybeRunning::from_scaled(self.height).into(),
430            width: ast::MaybeRunning::from_scaled(self.width).into(),
431            depth: ast::MaybeRunning::from_scaled(self.depth).into(),
432        }
433    }
434}
435
436impl<'a> ToBoxworks for ast::Rule<'a> {
437    type Output = ds::Rule;
438    fn to_boxworks(&self) -> Self::Output {
439        ds::Rule {
440            height: self.height.value.to_scaled(),
441            width: self.width.value.to_scaled(),
442            depth: self.depth.value.to_scaled(),
443        }
444    }
445}
446
447impl ToBoxLang for ds::Mark {
448    type Output = ast::Mark<'static>;
449    fn to_box_lang(&self) -> Self::Output {
450        ast::Mark::default()
451    }
452}
453
454impl<'a> ToBoxworks for ast::Mark<'a> {
455    type Output = ds::Mark;
456    fn to_boxworks(&self) -> Self::Output {
457        ds::Mark { list: vec![] }
458    }
459}
460
461impl ToBoxLang for ds::Adjust {
462    type Output = ast::Adjust<'static>;
463    fn to_box_lang(&self) -> Self::Output {
464        ast::Adjust {
465            content: self.list.to_box_lang().into(),
466        }
467    }
468}
469
470impl<'a> ToBoxworks for ast::Adjust<'a> {
471    type Output = ds::Adjust;
472    fn to_boxworks(&self) -> Self::Output {
473        ds::Adjust {
474            list: self.content.value.to_boxworks(),
475        }
476    }
477}
478
479impl ToBoxLang for ds::Insertion {
480    type Output = ast::Insertion<'static>;
481    fn to_box_lang(&self) -> Self::Output {
482        ast::Insertion {
483            box_number: (self.box_number as i32).into(),
484            height: self.height.into(),
485            split_max_depth: self.split_max_depth.into(),
486            split_top_skip_width: self.split_top_skip.width.into(),
487            split_top_skip_stretch: (
488                self.split_top_skip.stretch,
489                self.split_top_skip.stretch_order,
490            )
491                .into(),
492            split_top_skip_shrink: (self.split_top_skip.shrink, self.split_top_skip.shrink_order)
493                .into(),
494            float_penalty: (self.float_penalty as i32).into(),
495            vbox: self.vbox.to_box_lang().into(),
496        }
497    }
498}
499
500impl<'a> ToBoxworks for ast::Insertion<'a> {
501    type Output = ds::Insertion;
502    fn to_boxworks(&self) -> Self::Output {
503        ds::Insertion {
504            box_number: self.box_number.value as u8,
505            height: self.height.value,
506            split_max_depth: self.split_max_depth.value,
507            split_top_skip: common::Glue {
508                width: self.split_top_skip_width.value,
509                stretch: self.split_top_skip_stretch.value.0,
510                stretch_order: self.split_top_skip_stretch.value.1,
511                shrink: self.split_top_skip_shrink.value.0,
512                shrink_order: self.split_top_skip_shrink.value.1,
513            },
514            float_penalty: self.float_penalty.value as u32,
515            vbox: self.vbox.value.to_boxworks(),
516        }
517    }
518}
519
520impl ToBoxLang for ds::Math {
521    type Output = ast::Math<'static>;
522    fn to_box_lang(&self) -> Self::Output {
523        ast::Math {
524            kind: match self {
525                ds::Math::Before => Cow::Borrowed("before"),
526                ds::Math::After => Cow::Borrowed("after"),
527            }
528            .into(),
529        }
530    }
531}
532
533impl<'a> ToBoxworks for ast::Math<'a> {
534    type Output = ds::Math;
535    fn to_boxworks(&self) -> Self::Output {
536        match self.kind.value.as_ref() {
537            "after" => ds::Math::After,
538            _ => ds::Math::Before,
539        }
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546
547    macro_rules! tests {
548        ( $( ($name: ident, $input: expr, $want: expr,), )+ ) => {
549            $(
550                #[test]
551                fn $name() {
552                    let input: Vec<ds::Horizontal> = $input;
553                    let want: Vec<ast::Horizontal<'static>> = $want;
554                    let got = input.to_box_lang();
555                    assert_eq!(got, want);
556                }
557            )+
558        };
559    }
560
561    tests!(
562        (
563            chars_same_font,
564            vec![
565                ds::Char { char: 'B', font: 0 }.into(),
566                ds::Char { char: 'o', font: 0 }.into(),
567                ds::Char { char: 'x', font: 0 }.into(),
568            ],
569            vec![ast::Chars {
570                content: Cow::Borrowed("Box").into(),
571                font: 0_i32.into(),
572            }
573            .into()],
574        ),
575        (
576            chars_different_font,
577            vec![
578                ds::Char { char: 'B', font: 0 }.into(),
579                ds::Char { char: 'o', font: 0 }.into(),
580                ds::Char { char: 'x', font: 0 }.into(),
581                ds::Char { char: 'e', font: 1 }.into(),
582                ds::Char { char: 'd', font: 1 }.into(),
583            ],
584            vec![
585                ast::Chars {
586                    content: Cow::Borrowed("Box").into(),
587                    font: 0_i32.into(),
588                }
589                .into(),
590                ast::Chars {
591                    content: Cow::Borrowed("ed").into(),
592                    font: 1_i32.into(),
593                }
594                .into(),
595            ],
596        ),
597    );
598}