1use std::borrow::Cow;
4
5use crate::ast;
6use boxworks::ds;
7
8pub trait ToBoxLang {
10 type Output;
11 fn to_box_lang(&self) -> Self::Output;
12}
13
14pub 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 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 ..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 ..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, included_right_boundary: false, 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}