1use crate::{FopError, Length, Percentage, Result};
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq)]
35pub enum Expression {
36 Literal(Length),
38
39 Percentage(Percentage),
41
42 Add(Box<Expression>, Box<Expression>),
44
45 Sub(Box<Expression>, Box<Expression>),
47
48 Mul(Box<Expression>, f64),
50
51 Div(Box<Expression>, f64),
53}
54
55#[derive(Debug, Clone, Copy)]
60pub struct EvalContext {
61 pub base_width: Option<Length>,
63
64 pub base_height: Option<Length>,
66
67 pub font_size: Length,
69}
70
71impl EvalContext {
72 #[inline]
74 #[must_use = "this returns a new value without modifying anything"]
75 pub const fn new(
76 base_width: Option<Length>,
77 base_height: Option<Length>,
78 font_size: Length,
79 ) -> Self {
80 Self {
81 base_width,
82 base_height,
83 font_size,
84 }
85 }
86
87 #[inline]
89 #[must_use = "this returns a new value without modifying anything"]
90 pub const fn with_width(base_width: Length, font_size: Length) -> Self {
91 Self {
92 base_width: Some(base_width),
93 base_height: None,
94 font_size,
95 }
96 }
97
98 #[inline]
100 #[must_use = "this returns a new value without modifying anything"]
101 pub const fn with_height(base_height: Length, font_size: Length) -> Self {
102 Self {
103 base_width: None,
104 base_height: Some(base_height),
105 font_size,
106 }
107 }
108}
109
110impl Expression {
111 pub fn parse(input: &str) -> Result<Self> {
123 let input = input.trim();
124
125 if let Some(stripped) = input.strip_prefix("calc(") {
127 if let Some(content) = stripped.strip_suffix(')') {
128 return Self::parse_expression(content.trim());
129 }
130 }
131
132 Err(FopError::ParseError(format!(
133 "Expression must be wrapped in calc(): {}",
134 input
135 )))
136 }
137
138 fn parse_expression(input: &str) -> Result<Self> {
140 if let Some(pos) = Self::find_operator(input, &['+', '-']) {
142 let op = input.chars().nth(pos).ok_or_else(|| {
143 FopError::ParseError(format!(
144 "Operator not found at position {} in: {}",
145 pos, input
146 ))
147 })?;
148 let left = Self::parse_expression(input[..pos].trim())?;
149 let right = Self::parse_expression(input[pos + 1..].trim())?;
150
151 return match op {
152 '+' => Ok(Expression::Add(Box::new(left), Box::new(right))),
153 '-' => Ok(Expression::Sub(Box::new(left), Box::new(right))),
154 _ => unreachable!(),
155 };
156 }
157
158 if let Some(pos) = Self::find_operator(input, &['*', '/']) {
160 let op = input.chars().nth(pos).ok_or_else(|| {
161 FopError::ParseError(format!(
162 "Operator not found at position {} in: {}",
163 pos, input
164 ))
165 })?;
166 let left = Self::parse_expression(input[..pos].trim())?;
167 let right_str = input[pos + 1..].trim();
168
169 let scalar = right_str.parse::<f64>().map_err(|_| {
171 FopError::ParseError(format!(
172 "Right side of {} must be a number: {}",
173 op, right_str
174 ))
175 })?;
176
177 return match op {
178 '*' => Ok(Expression::Mul(Box::new(left), scalar)),
179 '/' => {
180 if scalar == 0.0 {
181 Err(FopError::ParseError("Division by zero".to_string()))
182 } else {
183 Ok(Expression::Div(Box::new(left), scalar))
184 }
185 }
186 _ => unreachable!(),
187 };
188 }
189
190 if let Some(stripped) = input.strip_prefix('(') {
192 if let Some(content) = stripped.strip_suffix(')') {
193 return Self::parse_expression(content.trim());
194 }
195 }
196
197 if input.ends_with('%') {
199 let pct_str = input.trim_end_matches('%').trim();
200 let pct_value = pct_str
201 .parse::<f64>()
202 .map_err(|_| FopError::ParseError(format!("Invalid percentage: {}", input)))?;
203 return Ok(Expression::Percentage(Percentage::from_percent(pct_value)));
204 }
205
206 Self::parse_length(input)
208 }
209
210 fn find_operator(input: &str, operators: &[char]) -> Option<usize> {
212 let mut depth = 0;
213 let mut last_op_pos = None;
214
215 for (i, c) in input.chars().enumerate() {
216 match c {
217 '(' => depth += 1,
218 ')' => depth -= 1,
219 _ if depth == 0 && operators.contains(&c) => {
220 last_op_pos = Some(i);
221 }
222 _ => {}
223 }
224 }
225
226 last_op_pos
227 }
228
229 fn parse_length(input: &str) -> Result<Self> {
231 let input = input.trim();
232
233 let mut split_pos = 0;
235 for (i, c) in input.chars().enumerate() {
236 if !c.is_numeric() && c != '.' && c != '-' && c != '+' {
237 split_pos = i;
238 break;
239 }
240 }
241
242 if split_pos == 0 {
243 return Err(FopError::ParseError(format!("Invalid length: {}", input)));
244 }
245
246 let value_str = &input[..split_pos];
247 let unit = &input[split_pos..];
248
249 let value = value_str
250 .parse::<f64>()
251 .map_err(|_| FopError::ParseError(format!("Invalid number: {}", value_str)))?;
252
253 let length = match unit {
254 "pt" => Length::from_pt(value),
255 "mm" => Length::from_mm(value),
256 "cm" => Length::from_cm(value),
257 "in" => Length::from_inch(value),
258 _ => return Err(FopError::ParseError(format!("Unknown unit: {}", unit))),
259 };
260
261 Ok(Expression::Literal(length))
262 }
263
264 pub fn evaluate(&self, context: &EvalContext) -> Result<Length> {
277 match self {
278 Expression::Literal(len) => Ok(*len),
279
280 Expression::Percentage(pct) => {
281 let base = context.base_width.ok_or_else(|| {
283 FopError::Generic("No base value available for percentage".to_string())
284 })?;
285 Ok(pct.of(base))
286 }
287
288 Expression::Add(left, right) => {
289 let left_val = left.evaluate(context)?;
290 let right_val = right.evaluate(context)?;
291 Ok(left_val + right_val)
292 }
293
294 Expression::Sub(left, right) => {
295 let left_val = left.evaluate(context)?;
296 let right_val = right.evaluate(context)?;
297 Ok(left_val - right_val)
298 }
299
300 Expression::Mul(expr, scalar) => {
301 let val = expr.evaluate(context)?;
302 let millipoints = (val.millipoints() as f64 * scalar).round() as i32;
303 Ok(Length::from_millipoints(millipoints))
304 }
305
306 Expression::Div(expr, scalar) => {
307 let val = expr.evaluate(context)?;
308 let millipoints = (val.millipoints() as f64 / scalar).round() as i32;
309 Ok(Length::from_millipoints(millipoints))
310 }
311 }
312 }
313}
314
315impl fmt::Display for Expression {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 match self {
318 Expression::Literal(len) => write!(f, "{}", len),
319 Expression::Percentage(pct) => write!(f, "{}", pct),
320 Expression::Add(left, right) => write!(f, "({} + {})", left, right),
321 Expression::Sub(left, right) => write!(f, "({} - {})", left, right),
322 Expression::Mul(expr, scalar) => write!(f, "({} * {})", expr, scalar),
323 Expression::Div(expr, scalar) => write!(f, "({} / {})", expr, scalar),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_parse_literal() {
334 let expr = Expression::parse("calc(20pt)").expect("test: should succeed");
335 assert_eq!(expr, Expression::Literal(Length::from_pt(20.0)));
336 }
337
338 #[test]
339 fn test_parse_percentage() {
340 let expr = Expression::parse("calc(50%)").expect("test: should succeed");
341 assert_eq!(expr, Expression::Percentage(Percentage::from_percent(50.0)));
342 }
343
344 #[test]
345 fn test_parse_addition() {
346 let expr = Expression::parse("calc(50% + 10pt)").expect("test: should succeed");
347 match expr {
348 Expression::Add(left, right) => {
349 assert_eq!(
350 *left,
351 Expression::Percentage(Percentage::from_percent(50.0))
352 );
353 assert_eq!(*right, Expression::Literal(Length::from_pt(10.0)));
354 }
355 _ => panic!("Expected Add expression"),
356 }
357 }
358
359 #[test]
360 fn test_parse_subtraction() {
361 let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
362 match expr {
363 Expression::Sub(left, right) => {
364 assert_eq!(
365 *left,
366 Expression::Percentage(Percentage::from_percent(100.0))
367 );
368 assert_eq!(*right, Expression::Literal(Length::from_pt(20.0)));
369 }
370 _ => panic!("Expected Sub expression"),
371 }
372 }
373
374 #[test]
375 fn test_parse_multiplication() {
376 let expr = Expression::parse("calc(50% * 2)").expect("test: should succeed");
377 match expr {
378 Expression::Mul(inner, scalar) => {
379 assert_eq!(
380 *inner,
381 Expression::Percentage(Percentage::from_percent(50.0))
382 );
383 assert!((scalar - 2.0).abs() < 0.001);
384 }
385 _ => panic!("Expected Mul expression"),
386 }
387 }
388
389 #[test]
390 fn test_parse_division() {
391 let expr = Expression::parse("calc(100% / 3)").expect("test: should succeed");
392 match expr {
393 Expression::Div(inner, scalar) => {
394 assert_eq!(
395 *inner,
396 Expression::Percentage(Percentage::from_percent(100.0))
397 );
398 assert!((scalar - 3.0).abs() < 0.001);
399 }
400 _ => panic!("Expected Div expression"),
401 }
402 }
403
404 #[test]
405 fn test_parse_nested_expression() {
406 let expr = Expression::parse("calc((100% - 40pt) / 2)").expect("test: should succeed");
407 match expr {
408 Expression::Div(inner, scalar) => {
409 assert!((scalar - 2.0).abs() < 0.001);
410 match *inner {
411 Expression::Sub(left, right) => {
412 assert_eq!(
413 *left,
414 Expression::Percentage(Percentage::from_percent(100.0))
415 );
416 assert_eq!(*right, Expression::Literal(Length::from_pt(40.0)));
417 }
418 _ => panic!("Expected Sub expression inside Div"),
419 }
420 }
421 _ => panic!("Expected Div expression"),
422 }
423 }
424
425 #[test]
426 fn test_parse_mixed_units() {
427 let expr = Expression::parse("calc(100% - 10mm)").expect("test: should succeed");
428 match expr {
429 Expression::Sub(left, right) => {
430 assert_eq!(
431 *left,
432 Expression::Percentage(Percentage::from_percent(100.0))
433 );
434 assert_eq!(*right, Expression::Literal(Length::from_mm(10.0)));
435 }
436 _ => panic!("Expected Sub expression"),
437 }
438 }
439
440 #[test]
441 fn test_parse_invalid_no_calc() {
442 let result = Expression::parse("50%");
443 assert!(result.is_err());
444 }
445
446 #[test]
447 fn test_parse_invalid_division_by_zero() {
448 let result = Expression::parse("calc(100% / 0)");
449 assert!(result.is_err());
450 }
451
452 #[test]
453 fn test_evaluate_literal() {
454 let expr = Expression::Literal(Length::from_pt(20.0));
455 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
456 let result = expr.evaluate(&ctx).expect("test: should succeed");
457 assert_eq!(result, Length::from_pt(20.0));
458 }
459
460 #[test]
461 fn test_evaluate_percentage() {
462 let expr = Expression::Percentage(Percentage::from_percent(50.0));
463 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
464 let result = expr.evaluate(&ctx).expect("test: should succeed");
465 assert_eq!(result, Length::from_pt(50.0));
466 }
467
468 #[test]
469 fn test_evaluate_addition() {
470 let expr = Expression::parse("calc(50% + 10pt)").expect("test: should succeed");
471 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
472 let result = expr.evaluate(&ctx).expect("test: should succeed");
473 assert_eq!(result, Length::from_pt(60.0));
475 }
476
477 #[test]
478 fn test_evaluate_subtraction() {
479 let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
480 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
481 let result = expr.evaluate(&ctx).expect("test: should succeed");
482 assert_eq!(result, Length::from_pt(180.0));
484 }
485
486 #[test]
487 fn test_evaluate_multiplication() {
488 let expr = Expression::parse("calc(50pt * 2)").expect("test: should succeed");
489 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
490 let result = expr.evaluate(&ctx).expect("test: should succeed");
491 assert_eq!(result, Length::from_pt(100.0));
492 }
493
494 #[test]
495 fn test_evaluate_division() {
496 let expr = Expression::parse("calc(100pt / 3)").expect("test: should succeed");
497 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
498 let result = expr.evaluate(&ctx).expect("test: should succeed");
499 assert!((result.to_pt() - 33.333).abs() < 0.01);
500 }
501
502 #[test]
503 fn test_evaluate_nested() {
504 let expr = Expression::parse("calc((100% - 40pt) / 2)").expect("test: should succeed");
505 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
506 let result = expr.evaluate(&ctx).expect("test: should succeed");
507 assert_eq!(result, Length::from_pt(80.0));
509 }
510
511 #[test]
512 fn test_evaluate_complex_nested() {
513 let expr = Expression::parse("calc(50% + (20pt * 2))").expect("test: should succeed");
514 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
515 let result = expr.evaluate(&ctx).expect("test: should succeed");
516 assert_eq!(result, Length::from_pt(90.0));
518 }
519
520 #[test]
521 fn test_evaluate_percentage_no_base() {
522 let expr = Expression::Percentage(Percentage::from_percent(50.0));
523 let ctx = EvalContext::new(None, None, Length::from_pt(12.0));
524 let result = expr.evaluate(&ctx);
525 assert!(result.is_err());
526 }
527
528 #[test]
529 fn test_display() {
530 let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
531 let display = format!("{}", expr);
532 assert!(display.contains("100%"));
533 assert!(display.contains("20pt"));
534 }
535
536 #[test]
537 fn test_parse_millimeters() {
538 let expr = Expression::parse("calc(10mm)").expect("test: should succeed");
539 assert_eq!(expr, Expression::Literal(Length::from_mm(10.0)));
540 }
541
542 #[test]
543 fn test_parse_centimeters() {
544 let expr = Expression::parse("calc(2.5cm)").expect("test: should succeed");
545 assert_eq!(expr, Expression::Literal(Length::from_cm(2.5)));
546 }
547
548 #[test]
549 fn test_parse_inches() {
550 let expr = Expression::parse("calc(1in)").expect("test: should succeed");
551 assert_eq!(expr, Expression::Literal(Length::from_inch(1.0)));
552 }
553}
554
555#[cfg(test)]
556mod expression_extra_tests {
557 use super::*;
558
559 #[test]
562 fn test_eval_context_new() {
563 let ctx = EvalContext::new(
564 Some(Length::from_pt(100.0)),
565 Some(Length::from_pt(200.0)),
566 Length::from_pt(12.0),
567 );
568 assert_eq!(ctx.base_width, Some(Length::from_pt(100.0)));
569 assert_eq!(ctx.base_height, Some(Length::from_pt(200.0)));
570 assert_eq!(ctx.font_size, Length::from_pt(12.0));
571 }
572
573 #[test]
574 fn test_eval_context_with_width() {
575 let ctx = EvalContext::with_width(Length::from_pt(400.0), Length::from_pt(10.0));
576 assert_eq!(ctx.base_width, Some(Length::from_pt(400.0)));
577 assert!(ctx.base_height.is_none());
578 }
579
580 #[test]
581 fn test_eval_context_with_height() {
582 let ctx = EvalContext::with_height(Length::from_pt(300.0), Length::from_pt(10.0));
583 assert!(ctx.base_width.is_none());
584 assert_eq!(ctx.base_height, Some(Length::from_pt(300.0)));
585 }
586
587 #[test]
588 fn test_eval_context_no_base() {
589 let ctx = EvalContext::new(None, None, Length::from_pt(12.0));
590 assert!(ctx.base_width.is_none());
591 assert!(ctx.base_height.is_none());
592 }
593
594 #[test]
597 fn test_literal_expression_evaluate() {
598 let expr = Expression::Literal(Length::from_mm(10.0));
599 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
600 let result = expr.evaluate(&ctx).expect("test: should succeed");
601 assert!((result.to_mm() - 10.0).abs() < 0.01);
602 }
603
604 #[test]
605 fn test_percentage_expression_25_percent() {
606 let expr = Expression::Percentage(Percentage::from_percent(25.0));
607 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
608 let result = expr.evaluate(&ctx).expect("test: should succeed");
609 assert!((result.to_pt() - 50.0).abs() < 0.01);
610 }
611
612 #[test]
613 fn test_percentage_no_base_errors() {
614 let expr = Expression::Percentage(Percentage::from_percent(50.0));
615 let ctx = EvalContext::new(None, None, Length::from_pt(12.0));
616 assert!(expr.evaluate(&ctx).is_err());
617 }
618
619 #[test]
620 fn test_add_two_literals() {
621 let expr = Expression::Add(
622 Box::new(Expression::Literal(Length::from_pt(30.0))),
623 Box::new(Expression::Literal(Length::from_pt(20.0))),
624 );
625 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
626 let result = expr.evaluate(&ctx).expect("test: should succeed");
627 assert!((result.to_pt() - 50.0).abs() < 0.01);
628 }
629
630 #[test]
631 fn test_sub_two_literals() {
632 let expr = Expression::Sub(
633 Box::new(Expression::Literal(Length::from_pt(30.0))),
634 Box::new(Expression::Literal(Length::from_pt(12.0))),
635 );
636 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
637 let result = expr.evaluate(&ctx).expect("test: should succeed");
638 assert!((result.to_pt() - 18.0).abs() < 0.01);
639 }
640
641 #[test]
642 fn test_mul_literal_by_scalar() {
643 let expr = Expression::Mul(Box::new(Expression::Literal(Length::from_pt(15.0))), 4.0);
644 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
645 let result = expr.evaluate(&ctx).expect("test: should succeed");
646 assert!((result.to_pt() - 60.0).abs() < 0.01);
647 }
648
649 #[test]
650 fn test_div_literal_by_scalar() {
651 let expr = Expression::Div(Box::new(Expression::Literal(Length::from_pt(90.0))), 3.0);
652 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
653 let result = expr.evaluate(&ctx).expect("test: should succeed");
654 assert!((result.to_pt() - 30.0).abs() < 0.01);
655 }
656
657 #[test]
660 fn test_parse_literal_mm() {
661 let expr = Expression::parse("calc(25mm)").expect("test: should succeed");
662 assert_eq!(expr, Expression::Literal(Length::from_mm(25.0)));
663 }
664
665 #[test]
666 fn test_parse_literal_cm() {
667 let expr = Expression::parse("calc(5cm)").expect("test: should succeed");
668 assert_eq!(expr, Expression::Literal(Length::from_cm(5.0)));
669 }
670
671 #[test]
672 fn test_parse_literal_in() {
673 let expr = Expression::parse("calc(2in)").expect("test: should succeed");
674 assert_eq!(expr, Expression::Literal(Length::from_inch(2.0)));
675 }
676
677 #[test]
678 fn test_parse_literal_pt() {
679 let expr = Expression::parse("calc(72pt)").expect("test: should succeed");
680 assert_eq!(expr, Expression::Literal(Length::from_pt(72.0)));
681 }
682
683 #[test]
684 fn test_parse_100_percent() {
685 let expr = Expression::parse("calc(100%)").expect("test: should succeed");
686 assert_eq!(
687 expr,
688 Expression::Percentage(Percentage::from_percent(100.0))
689 );
690 }
691
692 #[test]
693 fn test_parse_0_percent() {
694 let expr = Expression::parse("calc(0%)").expect("test: should succeed");
695 assert_eq!(expr, Expression::Percentage(Percentage::ZERO));
696 }
697
698 #[test]
699 fn test_parse_requires_calc_wrapper() {
700 assert!(Expression::parse("100%").is_err());
701 assert!(Expression::parse("10pt").is_err());
702 assert!(Expression::parse("50% + 10pt").is_err());
703 }
704
705 #[test]
706 fn test_parse_div_by_zero_errors() {
707 assert!(Expression::parse("calc(100% / 0)").is_err());
708 assert!(Expression::parse("calc(100pt / 0)").is_err());
709 }
710
711 #[test]
712 fn test_parse_add_mm_plus_pt() {
713 let expr = Expression::parse("calc(10mm + 20pt)").expect("test: should succeed");
714 match expr {
715 Expression::Add(left, right) => {
716 assert_eq!(*left, Expression::Literal(Length::from_mm(10.0)));
717 assert_eq!(*right, Expression::Literal(Length::from_pt(20.0)));
718 }
719 _ => panic!("Expected Add"),
720 }
721 }
722
723 #[test]
726 fn test_eval_100pct_minus_margin_a4_width() {
727 let expr = Expression::parse("calc(100% - 40pt)").expect("test: should succeed");
729 let ctx = EvalContext::with_width(Length::from_pt(595.0), Length::from_pt(12.0));
730 let result = expr.evaluate(&ctx).expect("test: should succeed");
731 assert!((result.to_pt() - 555.0).abs() < 0.1);
732 }
733
734 #[test]
735 fn test_eval_mul_gives_larger_result() {
736 let expr = Expression::parse("calc(10pt * 5)").expect("test: should succeed");
737 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
738 let result = expr.evaluate(&ctx).expect("test: should succeed");
739 assert!((result.to_pt() - 50.0).abs() < 0.01);
740 }
741
742 #[test]
743 fn test_eval_nested_add_in_div() {
744 let expr = Expression::parse("calc((10pt + 20pt) / 3)").expect("test: should succeed");
746 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
747 let result = expr.evaluate(&ctx).expect("test: should succeed");
748 assert!((result.to_pt() - 10.0).abs() < 0.1);
749 }
750
751 #[test]
754 fn test_display_literal() {
755 let expr = Expression::Literal(Length::from_pt(10.0));
756 let s = format!("{}", expr);
757 assert!(s.contains("10pt"));
758 }
759
760 #[test]
761 fn test_display_percentage() {
762 let expr = Expression::Percentage(Percentage::from_percent(50.0));
763 let s = format!("{}", expr);
764 assert!(s.contains("50%"));
765 }
766
767 #[test]
768 fn test_display_add() {
769 let expr = Expression::Add(
770 Box::new(Expression::Literal(Length::from_pt(10.0))),
771 Box::new(Expression::Literal(Length::from_pt(20.0))),
772 );
773 let s = format!("{}", expr);
774 assert!(s.contains('+'));
775 }
776
777 #[test]
778 fn test_display_sub() {
779 let expr = Expression::Sub(
780 Box::new(Expression::Literal(Length::from_pt(10.0))),
781 Box::new(Expression::Literal(Length::from_pt(5.0))),
782 );
783 let s = format!("{}", expr);
784 assert!(s.contains('-'));
785 }
786
787 #[test]
788 fn test_display_mul() {
789 let expr = Expression::Mul(Box::new(Expression::Literal(Length::from_pt(10.0))), 3.0);
790 let s = format!("{}", expr);
791 assert!(s.contains('*'));
792 }
793
794 #[test]
795 fn test_display_div() {
796 let expr = Expression::Div(Box::new(Expression::Literal(Length::from_pt(10.0))), 2.0);
797 let s = format!("{}", expr);
798 assert!(s.contains('/'));
799 }
800
801 #[test]
804 fn test_expression_clone() {
805 let expr = Expression::Literal(Length::from_pt(12.0));
806 let cloned = expr.clone();
807 assert_eq!(expr, cloned);
808 }
809
810 #[test]
811 fn test_expression_inequality() {
812 let a = Expression::Literal(Length::from_pt(10.0));
813 let b = Expression::Literal(Length::from_pt(20.0));
814 assert_ne!(a, b);
815 }
816}
817
818#[cfg(test)]
819mod expression_eval_tests {
820 use super::*;
821
822 #[test]
825 fn test_parse_pt_literal() {
826 let expr = Expression::parse("calc(12pt)").expect("test: should succeed");
827 assert_eq!(expr, Expression::Literal(Length::from_pt(12.0)));
828 }
829
830 #[test]
831 fn test_parse_mm_literal() {
832 let expr = Expression::parse("calc(10mm)").expect("test: should succeed");
833 assert_eq!(expr, Expression::Literal(Length::from_mm(10.0)));
834 }
835
836 #[test]
837 fn test_parse_cm_literal() {
838 let expr = Expression::parse("calc(2cm)").expect("test: should succeed");
839 assert_eq!(expr, Expression::Literal(Length::from_cm(2.0)));
840 }
841
842 #[test]
843 fn test_parse_in_literal() {
844 let expr = Expression::parse("calc(1in)").expect("test: should succeed");
845 assert_eq!(expr, Expression::Literal(Length::from_inch(1.0)));
846 }
847
848 #[test]
849 fn test_parse_zero_percent() {
850 let expr = Expression::parse("calc(0%)").expect("test: should succeed");
851 assert_eq!(expr, Expression::Percentage(Percentage::ZERO));
852 }
853
854 #[test]
855 fn test_parse_100_percent() {
856 let expr = Expression::parse("calc(100%)").expect("test: should succeed");
857 assert_eq!(
858 expr,
859 Expression::Percentage(Percentage::from_percent(100.0))
860 );
861 }
862
863 #[test]
864 fn test_parse_fractional_percent() {
865 let expr = Expression::parse("calc(33%)").expect("test: should succeed");
866 match expr {
867 Expression::Percentage(p) => {
868 assert!((p.to_percent() - 33.0).abs() < 0.001);
869 }
870 _ => panic!("Expected Percentage"),
871 }
872 }
873
874 #[test]
877 fn test_parse_add_pt_plus_pt() {
878 let expr = Expression::parse("calc(10pt + 20pt)").expect("test: should succeed");
879 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
880 let result = expr.evaluate(&ctx).expect("test: should succeed");
881 assert!((result.to_pt() - 30.0).abs() < 0.01);
882 }
883
884 #[test]
885 fn test_parse_add_pct_plus_pt() {
886 let expr = Expression::parse("calc(50% + 10pt)").expect("test: should succeed");
887 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
888 let result = expr.evaluate(&ctx).expect("test: should succeed");
889 assert!((result.to_pt() - 110.0).abs() < 0.01);
891 }
892
893 #[test]
894 fn test_parse_add_mm_plus_pt() {
895 let expr = Expression::parse("calc(10mm + 20pt)").expect("test: should succeed");
896 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
897 let result = expr.evaluate(&ctx).expect("test: should succeed");
898 let expected = Length::from_mm(10.0) + Length::from_pt(20.0);
899 assert!((result.to_pt() - expected.to_pt()).abs() < 0.1);
900 }
901
902 #[test]
905 fn test_parse_subtract_pt_from_pct() {
906 let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
907 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
908 let result = expr.evaluate(&ctx).expect("test: should succeed");
909 assert!((result.to_pt() - 80.0).abs() < 0.01);
910 }
911
912 #[test]
913 fn test_parse_subtract_larger_from_smaller_gives_negative() {
914 let expr = Expression::parse("calc(10pt - 30pt)").expect("test: should succeed");
915 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
916 let result = expr.evaluate(&ctx).expect("test: should succeed");
917 assert!(result.to_pt() < 0.0);
918 assert!((result.to_pt() - (-20.0)).abs() < 0.01);
919 }
920
921 #[test]
924 fn test_parse_mul_pt_by_scalar() {
925 let expr = Expression::parse("calc(10pt * 5)").expect("test: should succeed");
926 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
927 let result = expr.evaluate(&ctx).expect("test: should succeed");
928 assert!((result.to_pt() - 50.0).abs() < 0.01);
929 }
930
931 #[test]
932 fn test_parse_mul_pct_by_scalar() {
933 let expr = Expression::parse("calc(50% * 2)").expect("test: should succeed");
934 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
935 let result = expr.evaluate(&ctx).expect("test: should succeed");
936 assert!((result.to_pt() - 100.0).abs() < 0.01);
939 }
940
941 #[test]
944 fn test_parse_div_pt_by_scalar() {
945 let expr = Expression::parse("calc(60pt / 4)").expect("test: should succeed");
946 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
947 let result = expr.evaluate(&ctx).expect("test: should succeed");
948 assert!((result.to_pt() - 15.0).abs() < 0.01);
949 }
950
951 #[test]
952 fn test_parse_div_by_zero_fails() {
953 assert!(Expression::parse("calc(100% / 0)").is_err());
954 assert!(Expression::parse("calc(100pt / 0)").is_err());
955 }
956
957 #[test]
960 fn test_nested_paren_sub_then_div() {
961 let expr = Expression::parse("calc((100% - 40pt) / 2)").expect("test: should succeed");
963 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
964 let result = expr.evaluate(&ctx).expect("test: should succeed");
965 assert!((result.to_pt() - 80.0).abs() < 0.01);
967 }
968
969 #[test]
970 fn test_nested_paren_add_in_mul() {
971 let expr = Expression::parse("calc((10pt + 20pt) * 3)").expect("test: should succeed");
973 let ctx = EvalContext::with_width(Length::from_pt(100.0), Length::from_pt(12.0));
974 let result = expr.evaluate(&ctx).expect("test: should succeed");
975 assert!((result.to_pt() - 90.0).abs() < 0.01);
977 }
978
979 #[test]
980 fn test_a4_content_width_calc() {
981 let expr = Expression::parse("calc(100% - 40pt)").expect("test: should succeed");
984 let ctx = EvalContext::with_width(Length::from_pt(595.0), Length::from_pt(12.0));
985 let result = expr.evaluate(&ctx).expect("test: should succeed");
986 assert!((result.to_pt() - 555.0).abs() < 0.1);
987 }
988
989 #[test]
992 fn test_eval_percentage_no_base_returns_error() {
993 let expr = Expression::parse("calc(50%)").expect("test: should succeed");
994 let ctx = EvalContext::new(None, None, Length::from_pt(12.0));
995 assert!(expr.evaluate(&ctx).is_err());
996 }
997
998 #[test]
1001 fn test_parse_no_calc_wrapper_fails() {
1002 assert!(Expression::parse("100%").is_err());
1003 assert!(Expression::parse("10pt + 5pt").is_err());
1004 assert!(Expression::parse("50mm").is_err());
1005 }
1006
1007 #[test]
1010 fn test_display_contains_operands() {
1011 let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
1012 let s = format!("{}", expr);
1013 assert!(s.contains("100%"));
1014 assert!(s.contains("20pt"));
1015 assert!(s.contains('-'));
1016 }
1017
1018 #[test]
1019 fn test_display_literal_pt() {
1020 let expr = Expression::Literal(Length::from_pt(36.0));
1021 assert_eq!(format!("{}", expr), "36pt");
1022 }
1023
1024 #[test]
1025 fn test_display_percentage() {
1026 let expr = Expression::Percentage(Percentage::from_percent(75.0));
1027 let s = format!("{}", expr);
1028 assert!(s.contains("75%"));
1029 }
1030
1031 #[test]
1034 fn test_clone_and_eq() {
1035 let expr = Expression::parse("calc(50% + 10pt)").expect("test: should succeed");
1036 let cloned = expr.clone();
1037 assert_eq!(expr, cloned);
1038 }
1039
1040 #[test]
1041 fn test_ne_different_expressions() {
1042 let a = Expression::Literal(Length::from_pt(10.0));
1043 let b = Expression::Literal(Length::from_pt(20.0));
1044 assert_ne!(a, b);
1045 }
1046
1047 #[test]
1050 fn test_eval_context_with_width_has_no_height() {
1051 let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
1052 assert!(ctx.base_height.is_none());
1053 assert_eq!(ctx.base_width, Some(Length::from_pt(200.0)));
1054 }
1055
1056 #[test]
1057 fn test_eval_context_with_height_has_no_width() {
1058 let ctx = EvalContext::with_height(Length::from_pt(300.0), Length::from_pt(12.0));
1059 assert!(ctx.base_width.is_none());
1060 assert_eq!(ctx.base_height, Some(Length::from_pt(300.0)));
1061 }
1062}