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