1use std::collections::HashMap;
51use std::fmt;
52use std::sync::atomic::{AtomicU64, Ordering};
53use std::sync::{Arc, Mutex, OnceLock, RwLock};
54
55pub const MAX_CALCS: usize = 1 << 16;
57
58pub const FULL: &str = "too many distinct size expressions";
61
62pub fn is_full(err: &str) -> bool {
67 err.contains(FULL)
68}
69
70static REFUSED: AtomicU64 = AtomicU64::new(0);
73static LAST_REFUSED: Mutex<String> = Mutex::new(String::new());
74
75pub fn refused() -> (u64, String) {
78 let n = REFUSED.load(Ordering::Relaxed);
79 if n == 0 {
80 return (0, String::new());
82 }
83 let last = LAST_REFUSED.lock().map(|s| s.clone()).unwrap_or_default();
84 (n, last)
85}
86
87pub const MAX_DEPTH: u32 = 32;
95
96fn too_deep(what: &str) -> String {
97 format!("bad size{what}: nested past {MAX_DEPTH}")
98}
99
100fn finite(v: f32) -> Result<f32, String> {
107 if v.is_finite() {
108 Ok(if v == 0.0 { 0.0 } else { v })
110 } else {
111 Err(format!("bad size: {v} is not a finite number"))
112 }
113}
114
115fn canon(e: &mut Expr, depth: u32) -> Result<(), String> {
121 if depth > MAX_DEPTH {
122 return Err(too_deep(""));
123 }
124 match e {
125 Expr::Px(v) | Expr::Pct(v) => {
126 *v = finite(*v)?;
127 Ok(())
128 }
129 Expr::Min(xs) | Expr::Max(xs) => xs.iter_mut().try_for_each(|x| canon(x, depth + 1)),
130 Expr::Clamp(a, b, c) => {
131 canon(a, depth + 1)?;
132 canon(b, depth + 1)?;
133 canon(c, depth + 1)
134 }
135 }
136}
137
138#[derive(Clone, Debug, PartialEq)]
140pub enum Expr {
141 Px(f32),
142 Pct(f32),
144 Min(Vec<Expr>),
145 Max(Vec<Expr>),
146 Clamp(Box<Expr>, Box<Expr>, Box<Expr>),
148}
149
150impl Expr {
151 pub fn resolve(&self, room: f32) -> f32 {
153 self.eval(room).max(0.0)
154 }
155
156 fn eval(&self, room: f32) -> f32 {
157 match self {
158 Expr::Px(px) => *px,
159 Expr::Pct(f) => room * f,
160 Expr::Min(xs) => xs
161 .iter()
162 .map(|x| x.eval(room))
163 .fold(f32::INFINITY, f32::min),
164 Expr::Max(xs) => xs
165 .iter()
166 .map(|x| x.eval(room))
167 .fold(f32::NEG_INFINITY, f32::max),
168 Expr::Clamp(lo, target, hi) => target.eval(room).min(hi.eval(room)).max(lo.eval(room)),
170 }
171 }
172
173 pub fn relative(&self) -> bool {
175 match self {
176 Expr::Px(_) => false,
177 Expr::Pct(_) => true,
178 Expr::Min(xs) | Expr::Max(xs) => xs.iter().any(Expr::relative),
179 Expr::Clamp(a, b, c) => a.relative() || b.relative() || c.relative(),
180 }
181 }
182}
183
184impl Eq for Expr {}
188
189impl std::hash::Hash for Expr {
190 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
191 match self {
192 Expr::Px(v) => (0u8, v.to_bits()).hash(h),
193 Expr::Pct(v) => (1u8, v.to_bits()).hash(h),
194 Expr::Min(xs) => (2u8, xs).hash(h),
195 Expr::Max(xs) => (3u8, xs).hash(h),
196 Expr::Clamp(a, b, c) => (4u8, a, b, c).hash(h),
197 }
198 }
199}
200
201impl fmt::Display for Expr {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 fn list(f: &mut fmt::Formatter<'_>, name: &str, xs: &[&Expr]) -> fmt::Result {
204 write!(f, "{name}(")?;
205 for (i, x) in xs.iter().enumerate() {
206 if i > 0 {
207 write!(f, ", ")?;
208 }
209 write!(f, "{x}")?;
210 }
211 write!(f, ")")
212 }
213 match self {
214 Expr::Px(px) => write!(f, "{px}px"),
215 Expr::Pct(p) => write!(f, "{}%", p * 100.0),
216 Expr::Min(xs) => list(f, "min", &xs.iter().collect::<Vec<_>>()),
217 Expr::Max(xs) => list(f, "max", &xs.iter().collect::<Vec<_>>()),
218 Expr::Clamp(a, b, c) => list(f, "clamp", &[a, b, c]),
219 }
220 }
221}
222
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
226pub struct Calc(u32);
227
228impl Calc {
229 pub fn resolve(self, room: f32) -> f32 {
232 table()
233 .read()
234 .ok()
235 .and_then(|t| t.exprs.get(self.0 as usize).map(|e| e.resolve(room)))
236 .unwrap_or(0.0)
237 }
238
239 pub fn expr(self) -> Option<Arc<Expr>> {
241 table().read().ok()?.exprs.get(self.0 as usize).cloned()
242 }
243
244 pub fn id(self) -> u32 {
247 self.0
248 }
249
250 pub fn from_id(id: u32) -> Option<Calc> {
252 let t = table().read().ok()?;
253 ((id as usize) < t.exprs.len()).then_some(Calc(id))
254 }
255
256 pub fn describe(self) -> String {
258 self.expr()
259 .map_or_else(|| "calc(?)".into(), |e| e.to_string())
260 }
261}
262
263#[derive(Default)]
264struct Table {
265 exprs: Vec<Arc<Expr>>,
266 by_expr: HashMap<Expr, u32>,
267 by_input: HashMap<String, Norm>,
272 by_code: HashMap<Box<[u8]>, Norm>,
275}
276
277#[derive(Clone, Copy, Debug, PartialEq)]
279enum Norm {
280 Px(f32),
281 Pct(f32),
282 Calc(Calc),
283}
284
285fn norm(mut e: Expr) -> Result<Norm, String> {
286 canon(&mut e, 0)?;
289 Ok(match e {
290 Expr::Pct(f) => Norm::Pct(f),
291 e if !e.relative() => Norm::Px(e.resolve(0.0)),
292 e => Norm::Calc(intern_checked(e)?),
293 })
294}
295
296fn norm_str(s: &str) -> Result<Norm, String> {
298 if let Some(n) = table().read().ok().and_then(|t| t.by_input.get(s).copied()) {
299 return Ok(n);
300 }
301 let n = norm(parse(s)?)?;
302 if let Ok(mut t) = table().write()
303 && t.by_input.len() < MAX_CALCS
304 {
305 t.by_input.insert(s.to_string(), n);
306 }
307 Ok(n)
308}
309
310fn norm_sizing(n: Norm) -> crate::spec::Sizing {
311 use crate::spec::Sizing;
312 match n {
313 Norm::Px(px) => Sizing::Fixed(px),
314 Norm::Pct(f) => Sizing::Percent(f),
315 Norm::Calc(c) => Sizing::Calc(c),
316 }
317}
318
319fn norm_bound(n: Norm) -> Result<crate::spec::Bound, String> {
320 use crate::spec::Bound;
321 Ok(match n {
322 Norm::Px(px) => Bound::Px(px),
323 Norm::Pct(f) => Bound::Calc(intern(Expr::Pct(f))?),
325 Norm::Calc(c) => Bound::Calc(c),
326 })
327}
328
329pub fn from_value(v: &crate::value::Value) -> Result<Expr, String> {
333 value_at(v, 0)
334}
335
336fn value_at(v: &crate::value::Value, depth: u32) -> Result<Expr, String> {
337 use crate::value::Value;
338 if depth > MAX_DEPTH {
339 return Err(too_deep(""));
340 }
341 match v {
342 Value::Int(_) | Value::Float(_) => {
343 Ok(Expr::Px(finite(v.as_float().unwrap_or(0.0) as f32)?))
344 }
345 Value::Str(s) => parse(s),
346 Value::Map(m) => {
347 let mut it = m.iter();
348 let (Some((k, arg)), None) = (it.next(), it.next()) else {
349 return Err(
350 "bad size: a table names one of pct, percent, px, min, max, clamp".into(),
351 );
352 };
353 let num = || {
354 arg.as_float()
355 .map(|n| n as f32)
356 .ok_or_else(|| format!("bad size: {k} takes a number"))
357 .and_then(finite)
358 };
359 let args = || -> Result<Vec<Expr>, String> {
360 let Value::List(xs) = arg else {
361 return Err(format!("bad size: {k} takes a list"));
362 };
363 if xs.is_empty() {
364 return Err(format!("bad size: {k} takes at least one"));
365 }
366 xs.iter().map(|x| value_at(x, depth + 1)).collect()
367 };
368 match k.as_str() {
369 "pct" | "percent" => Ok(Expr::Pct(finite(num()? / 100.0)?)),
370 "px" => Ok(Expr::Px(num()?)),
371 "min" => Ok(Expr::Min(args()?)),
372 "max" => Ok(Expr::Max(args()?)),
373 "clamp" => match <[Expr; 3]>::try_from(args()?) {
374 Ok([a, b, c]) => Ok(Expr::Clamp(Box::new(a), Box::new(b), Box::new(c))),
375 Err(_) => Err("bad size: clamp takes three: clamp(MIN, TARGET, MAX)".into()),
376 },
377 _ => Err(format!(
378 "bad size: no {k:?} (pct, percent, px, min, max, clamp)"
379 )),
380 }
381 }
382 _ => Err("bad size: a number, a string or a table".into()),
383 }
384}
385
386pub fn from_code(code: &[f64]) -> Result<Expr, String> {
391 fn one(code: &[f64], at: &mut usize, depth: u32) -> Result<Expr, String> {
392 let mut next = || -> Result<f64, String> {
393 let v = code.get(*at).copied().ok_or("bad size code: truncated")?;
394 *at += 1;
395 Ok(v)
396 };
397 if depth > MAX_DEPTH {
398 return Err(too_deep(" code"));
399 }
400 Ok(match next()? as u32 {
401 1 => Expr::Px(finite(next()? as f32)?),
402 2 => Expr::Pct(finite(next()? as f32)?),
403 op @ (3 | 4) => {
404 let n = next()? as usize;
405 if n == 0 || n > code.len() {
406 return Err("bad size code: an argument count out of range".into());
407 }
408 let args = (0..n)
409 .map(|_| one(code, at, depth + 1))
410 .collect::<Result<Vec<_>, _>>()?;
411 if op == 3 {
412 Expr::Min(args)
413 } else {
414 Expr::Max(args)
415 }
416 }
417 5 => {
418 let a = one(code, at, depth + 1)?;
419 let b = one(code, at, depth + 1)?;
420 let c = one(code, at, depth + 1)?;
421 Expr::Clamp(Box::new(a), Box::new(b), Box::new(c))
422 }
423 op => return Err(format!("bad size code: no op {op}")),
424 })
425 }
426 let mut at = 0;
427 let e = one(code, &mut at, 0)?;
428 if at != code.len() {
429 return Err("bad size code: slots left over".into());
430 }
431 Ok(e)
432}
433
434fn norm_code(code: &[f64]) -> Result<Norm, String> {
436 let bytes =
439 unsafe { std::slice::from_raw_parts(code.as_ptr().cast::<u8>(), size_of_val(code)) };
440 if let Some(n) = table()
441 .read()
442 .ok()
443 .and_then(|t| t.by_code.get(bytes).copied())
444 {
445 return Ok(n);
446 }
447 let n = norm(from_code(code)?)?;
448 if let Ok(mut t) = table().write()
449 && t.by_code.len() < MAX_CALCS
450 {
451 t.by_code.insert(bytes.into(), n);
452 }
453 Ok(n)
454}
455
456pub fn sizing_code(code: &[f64]) -> Result<crate::spec::Sizing, String> {
458 norm_code(code).map(norm_sizing)
459}
460
461pub fn bound_code(code: &[f64]) -> Result<crate::spec::Bound, String> {
463 norm_bound(norm_code(code)?)
464}
465
466pub fn sizing_value(v: &crate::value::Value) -> Result<crate::spec::Sizing, String> {
468 match v {
469 crate::value::Value::Str(s) => sizing(s),
470 v => Ok(norm_sizing(norm(from_value(v)?)?)),
471 }
472}
473
474pub fn bound_value(v: &crate::value::Value) -> Result<crate::spec::Bound, String> {
476 match v {
477 crate::value::Value::Str(s) => bound(s),
478 v => norm_bound(norm(from_value(v)?)?),
479 }
480}
481
482pub fn sizing_of(e: Expr) -> Result<crate::spec::Sizing, String> {
485 Ok(norm_sizing(norm(e)?))
486}
487
488fn table() -> &'static RwLock<Table> {
489 static TABLE: OnceLock<RwLock<Table>> = OnceLock::new();
490 TABLE.get_or_init(Default::default)
491}
492
493pub fn intern(mut expr: Expr) -> Result<Calc, String> {
496 canon(&mut expr, 0)?;
497 intern_checked(expr)
498}
499
500fn intern_checked(expr: Expr) -> Result<Calc, String> {
502 if let Some(&id) = table()
503 .read()
504 .map_err(|e| e.to_string())?
505 .by_expr
506 .get(&expr)
507 {
508 return Ok(Calc(id));
509 }
510 let mut t = table().write().map_err(|e| e.to_string())?;
511 if let Some(&id) = t.by_expr.get(&expr) {
512 return Ok(Calc(id));
513 }
514 if t.exprs.len() >= MAX_CALCS {
515 drop(t);
516 return Err(refuse(&expr));
517 }
518 let id = t.exprs.len() as u32;
519 t.exprs.push(Arc::new(expr.clone()));
520 t.by_expr.insert(expr, id);
521 Ok(Calc(id))
522}
523
524#[cold]
527#[inline(never)]
528fn refuse(expr: &Expr) -> String {
529 let spelled = expr.to_string();
530 REFUSED.fetch_add(1, Ordering::Relaxed);
531 if let Ok(mut last) = LAST_REFUSED.lock() {
532 last.clone_from(&spelled);
533 }
534 format!(
535 "{FULL} ({MAX_CALCS}): \"{spelled}\" is not kept — declare one per layout, not one per \
536 frame"
537 )
538}
539
540pub fn parse(s: &str) -> Result<Expr, String> {
543 let mut p = Parser {
544 s: s.as_bytes(),
545 at: 0,
546 };
547 let e = p.expr(0)?;
548 p.skip_ws();
549 if p.at < p.s.len() {
550 return Err(p.error("the end"));
551 }
552 Ok(e)
553}
554
555pub fn sizing(s: &str) -> Result<crate::spec::Sizing, String> {
558 norm_str(s).map(norm_sizing)
559}
560
561pub fn bound(s: &str) -> Result<crate::spec::Bound, String> {
564 norm_bound(norm_str(s)?)
565}
566
567struct Parser<'a> {
568 s: &'a [u8],
569 at: usize,
570}
571
572impl Parser<'_> {
573 fn skip_ws(&mut self) {
574 while self.s.get(self.at).is_some_and(|c| c.is_ascii_whitespace()) {
575 self.at += 1;
576 }
577 }
578
579 fn error(&self, wanted: &str) -> String {
580 let rest = String::from_utf8_lossy(&self.s[self.at.min(self.s.len())..]);
581 if rest.is_empty() {
582 format!("bad size: {wanted} expected at the end")
583 } else {
584 format!("bad size: {wanted} expected at {rest:?}")
585 }
586 }
587
588 fn eat(&mut self, word: &str) -> bool {
589 self.skip_ws();
590 if self.s[self.at..].starts_with(word.as_bytes()) {
591 self.at += word.len();
592 true
593 } else {
594 false
595 }
596 }
597
598 fn expr(&mut self, depth: u32) -> Result<Expr, String> {
602 if depth > MAX_DEPTH {
603 return Err(too_deep(""));
604 }
605 self.skip_ws();
606 for name in ["clamp", "min", "max"] {
607 let rest = &self.s[self.at..];
611 if rest.len() > name.len()
612 && rest[..name.len()].eq_ignore_ascii_case(name.as_bytes())
613 && rest[name.len()] == b'('
614 {
615 self.at += name.len() + 1;
616 let mut args = vec![self.expr(depth + 1)?];
617 while self.eat(",") {
618 args.push(self.expr(depth + 1)?);
619 }
620 if !self.eat(")") {
621 return Err(self.error("\",\" or \")\""));
622 }
623 return match name {
624 "clamp" => match <[Expr; 3]>::try_from(args) {
625 Ok([a, b, c]) => Ok(Expr::Clamp(Box::new(a), Box::new(b), Box::new(c))),
626 Err(_) => {
627 Err("bad size: clamp takes three: clamp(MIN, TARGET, MAX)".into())
628 }
629 },
630 "min" => Ok(Expr::Min(args)),
631 _ => Ok(Expr::Max(args)),
632 };
633 }
634 }
635 let start = self.at;
636 while self
637 .s
638 .get(self.at)
639 .is_some_and(|c| c.is_ascii_digit() || *c == b'.')
640 {
641 self.at += 1;
642 }
643 let n: f32 = std::str::from_utf8(&self.s[start..self.at])
644 .ok()
645 .and_then(|t| t.parse().ok())
646 .ok_or_else(|| {
647 self.at = start;
648 self.error("a number, \"N%\", \"Npx\", min(…), max(…) or clamp(…)")
649 })?;
650 let n = finite(n)?;
653 let rest = &self.s[self.at..];
656 if rest.first() == Some(&b'%') {
657 self.at += 1;
658 Ok(Expr::Pct(finite(n / 100.0)?))
659 } else {
660 if rest.len() >= 2 && rest[..2].eq_ignore_ascii_case(b"px") {
661 self.at += 2;
662 }
663 Ok(Expr::Px(n))
664 }
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671 use crate::spec::{Bound, Sizing};
672
673 fn px(s: &str, room: f32) -> f32 {
674 parse(s).unwrap().resolve(room)
675 }
676
677 #[test]
678 fn expressions_resolve_against_the_room() {
679 let c = "clamp(400px, 80%, 1000px)";
680 assert_eq!(px(c, 300.0), 400.0, "the minimum");
681 assert_eq!(px(c, 1000.0), 800.0, "the target");
682 assert_eq!(px(c, 2000.0), 1000.0, "the maximum");
683 assert_eq!(
684 px("clamp(500, 10%, 200)", 1000.0),
685 500.0,
686 "the minimum over the maximum"
687 );
688 assert_eq!(px("min(720px, 100%)", 500.0), 500.0);
689 assert_eq!(px("max(50%, 300)", 400.0), 300.0);
690 assert_eq!(px("min(clamp(1, 50%, 900), 30%)", 1000.0), 300.0, "nested");
691 }
692
693 #[test]
694 fn only_what_depends_on_the_room_is_a_calc() {
695 assert_eq!(sizing("720px").unwrap(), Sizing::Fixed(720.0));
696 assert_eq!(sizing("min(300px, 400)").unwrap(), Sizing::Fixed(300.0));
697 assert_eq!(sizing("50%").unwrap(), Sizing::Percent(0.5));
698 let Sizing::Calc(a) = sizing("clamp(400px,80%,1000px)").unwrap() else {
699 panic!("a calc");
700 };
701 let Sizing::Calc(b) = sizing(" clamp( 400 , 80% , 1000px ) ").unwrap() else {
702 panic!("a calc");
703 };
704 assert_eq!(a, b, "one entry per expression, however spelled");
705 assert_eq!(a.describe(), "clamp(400px, 80%, 1000px)");
706 assert_eq!(bound("300").unwrap(), Bound::Px(300.0));
707 assert!(matches!(bound("50%").unwrap(), Bound::Calc(_)));
708 }
709
710 #[test]
711 fn the_same_expression_as_data() {
712 use crate::value::Value;
713 let list = |xs: Vec<Value>| Value::List(xs);
714 let map = |k: &str, v: Value| Value::Map([(k.to_string(), v)].into_iter().collect());
715 let v = map(
716 "clamp",
717 list(vec![
718 Value::Int(400),
719 map("pct", Value::Int(80)),
720 Value::Str("1000px".into()),
721 ]),
722 );
723 let Sizing::Calc(a) = sizing_value(&v).unwrap() else {
724 panic!("a calc");
725 };
726 assert_eq!(
727 Some(a),
728 match sizing("clamp(400px, 80%, 1000px)").unwrap() {
729 Sizing::Calc(c) => Some(c),
730 _ => None,
731 },
732 "one entry, spelled or built"
733 );
734 assert_eq!(
735 sizing_value(&map("percent", Value::Int(50))).unwrap(),
736 Sizing::Percent(0.5)
737 );
738 assert_eq!(
739 sizing_value(&map("min", list(vec![Value::Int(300), Value::Int(400)]))).unwrap(),
740 Sizing::Fixed(300.0)
741 );
742 assert!(
743 sizing_value(&map("clamp", list(vec![Value::Int(1)])))
744 .unwrap_err()
745 .contains("three")
746 );
747 assert!(
748 sizing_value(&map("wide", Value::Int(1)))
749 .unwrap_err()
750 .contains("no \"wide\"")
751 );
752 }
753
754 #[test]
755 fn the_same_expression_in_prefix_code() {
756 let code = [5.0, 1.0, 400.0, 2.0, 0.8, 3.0, 2.0, 1.0, 1000.0, 2.0, 1.0];
757 assert_eq!(
758 from_code(&code).unwrap().to_string(),
759 "clamp(400px, 80%, min(1000px, 100%))"
760 );
761 assert!(from_code(&code[..4]).unwrap_err().contains("truncated"));
762 assert!(
763 from_code(&[1.0, 3.0, 9.0])
764 .unwrap_err()
765 .contains("left over")
766 );
767 assert!(from_code(&[9.0]).unwrap_err().contains("no op 9"));
768 }
769
770 #[test]
771 fn a_bad_one_says_where() {
772 assert_eq!(
773 parse("80%x").unwrap_err(),
774 "bad size: the end expected at \"x\""
775 );
776 assert_eq!(
779 parse("80 %").unwrap_err(),
780 "bad size: the end expected at \"%\""
781 );
782 assert_eq!(
783 parse("100 px").unwrap_err(),
784 "bad size: the end expected at \"px\""
785 );
786 assert!(
787 parse("min (1, 2)")
788 .unwrap_err()
789 .contains("at \"min (1, 2)\"")
790 );
791 assert!(parse("1.2.3%").is_err());
792 assert!(parse("50px%").unwrap_err().contains("at \"%\""));
793 assert_eq!(
795 parse("MIN(10PX, 50%)").unwrap(),
796 parse("min(10px, 50%)").unwrap()
797 );
798 assert_eq!(
799 parse(" max( 1px ,2% ) ").unwrap(),
800 parse("max(1px, 2%)").unwrap()
801 );
802 assert!(parse("clamp(1, 2)").unwrap_err().contains("three"));
803 assert!(parse("wide").unwrap_err().contains("at \"wide\""));
804 assert!(parse("min(1, 2").unwrap_err().contains("at the end"));
805 }
806
807 fn nested(n: usize) -> (String, crate::value::Value, Vec<f64>, Expr) {
809 use crate::value::Value;
810 let spelled = format!("{}50%{}", "min(".repeat(n), ")".repeat(n));
811 let mut data = Value::Map(vec![("pct".into(), Value::Int(50))]);
812 let mut code = [3.0, 1.0].repeat(n);
813 code.extend([2.0, 0.5]);
814 let mut built = Expr::Pct(0.5);
815 for _ in 0..n {
816 data = Value::Map(vec![("min".into(), Value::List(vec![data]))]);
817 built = Expr::Min(vec![built]);
818 }
819 (spelled, data, code, built)
820 }
821
822 #[test]
826 fn nesting_stops_at_the_cap() {
827 let deep = parse(&"min(".repeat(100_000)).unwrap_err();
828 assert_eq!(deep, "bad size: nested past 32");
829 let (s, v, code, e) = nested(MAX_DEPTH as usize);
830 assert!(parse(&s).is_ok(), "32 is allowed");
831 assert!(from_value(&v).is_ok());
832 assert!(from_code(&code).is_ok());
833 assert!(intern(e).is_ok());
834 let (s, v, code, e) = nested(MAX_DEPTH as usize + 1);
835 assert_eq!(parse(&s).unwrap_err(), "bad size: nested past 32");
836 assert_eq!(from_value(&v).unwrap_err(), "bad size: nested past 32");
837 assert_eq!(
838 from_code(&code).unwrap_err(),
839 "bad size code: nested past 32"
840 );
841 assert_eq!(intern(e.clone()).unwrap_err(), "bad size: nested past 32");
842 assert!(sizing_of(e).is_err(), "a tree built by hand is held to it");
843 assert!(sizing(&s).is_err());
844 }
845
846 #[test]
852 fn only_finite_numbers_and_one_zero() {
853 use crate::value::Value;
854 let map = |k: &str, v: Value| Value::Map(vec![(k.to_string(), v)]);
855 let with = |n: f64| {
856 map(
857 "min",
858 Value::List(vec![Value::Float(n), map("pct", Value::Int(50))]),
859 )
860 };
861 for n in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1e300] {
862 let e = sizing_value(&with(n)).unwrap_err();
863 assert!(e.contains("not a finite number"), "{n}: {e}");
864 assert!(sizing_value(&map("px", Value::Float(n))).is_err());
865 assert!(sizing_value(&map("pct", Value::Float(n))).is_err());
866 assert!(from_code(&[3.0, 2.0, 1.0, n, 2.0, 0.5]).is_err());
867 assert!(from_code(&[2.0, n]).is_err());
868 }
869 assert!(bound_value(&with(f64::NAN)).is_err());
870 assert!(intern(Expr::Min(vec![Expr::Px(f32::NAN), Expr::Pct(0.5)])).is_err());
871 assert!(sizing_of(Expr::Pct(f32::INFINITY)).is_err());
872 let digits = format!("min(1{}px, 50%)", "0".repeat(40));
873 assert!(parse(&digits).unwrap_err().contains("not a finite number"));
874 assert!(parse(&format!("1{}%", "0".repeat(40))).is_err());
875
876 let zero = sizing_value(&with(0.0)).unwrap();
877 assert_eq!(sizing_value(&with(-0.0)).unwrap(), zero, "-0 is 0");
878 assert_eq!(
879 sizing_of(Expr::Min(vec![Expr::Px(-0.0), Expr::Pct(0.5)])).unwrap(),
880 zero
881 );
882 assert_eq!(sizing_code(&[3.0, 2.0, 1.0, -0.0, 2.0, 0.5]).unwrap(), zero);
883 assert_eq!(zero.describe(), "min(0px, 50%)");
884 }
885}