From be7cfc85d08c545abfac08098b7b33b4bd71f37e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 27 Oct 2024 19:04:55 +0100 Subject: Split out four new crates (#5302) --- crates/typst-library/src/foundations/ops.rs | 583 ++++++++++++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 crates/typst-library/src/foundations/ops.rs (limited to 'crates/typst-library/src/foundations/ops.rs') diff --git a/crates/typst-library/src/foundations/ops.rs b/crates/typst-library/src/foundations/ops.rs new file mode 100644 index 00000000..ba36137f --- /dev/null +++ b/crates/typst-library/src/foundations/ops.rs @@ -0,0 +1,583 @@ +//! Operations on values. + +use std::cmp::Ordering; + +use ecow::eco_format; +use typst_utils::Numeric; + +use crate::diag::{bail, HintedStrResult, StrResult}; +use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value}; +use crate::layout::{Alignment, Length, Rel}; +use crate::text::TextElem; +use crate::visualize::Stroke; + +/// Bail with a type mismatch error. +macro_rules! mismatch { + ($fmt:expr, $($value:expr),* $(,)?) => { + return Err(eco_format!($fmt, $($value.ty()),*).into()) + }; +} + +/// Join a value with another value. +pub fn join(lhs: Value, rhs: Value) -> StrResult { + use Value::*; + Ok(match (lhs, rhs) { + (a, None) => a, + (None, b) => b, + (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Str(a), Str(b)) => Str(a + b), + (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), + (Bytes(a), Bytes(b)) => Bytes(a + b), + (Content(a), Content(b)) => Content(a + b), + (Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())), + (Content(a), Str(b)) => Content(a + TextElem::packed(b)), + (Str(a), Content(b)) => Content(TextElem::packed(a) + b), + (Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b), + (Array(a), Array(b)) => Array(a + b), + (Dict(a), Dict(b)) => Dict(a + b), + + // Type compatibility. + (Type(a), Str(b)) => Str(format_str!("{a}{b}")), + (Str(a), Type(b)) => Str(format_str!("{a}{b}")), + + (a, b) => mismatch!("cannot join {} with {}", a, b), + }) +} + +/// Apply the unary plus operator to a value. +pub fn pos(value: Value) -> HintedStrResult { + use Value::*; + Ok(match value { + Int(v) => Int(v), + Float(v) => Float(v), + Decimal(v) => Decimal(v), + Length(v) => Length(v), + Angle(v) => Angle(v), + Ratio(v) => Ratio(v), + Relative(v) => Relative(v), + Fraction(v) => Fraction(v), + Symbol(_) | Str(_) | Bytes(_) | Content(_) | Array(_) | Dict(_) | Datetime(_) => { + mismatch!("cannot apply unary '+' to {}", value) + } + Dyn(d) => { + if d.is::() { + mismatch!("cannot apply unary '+' to {}", d) + } else { + mismatch!("cannot apply '+' to {}", d) + } + } + v => mismatch!("cannot apply '+' to {}", v), + }) +} + +/// Compute the negation of a value. +pub fn neg(value: Value) -> HintedStrResult { + use Value::*; + Ok(match value { + Int(v) => Int(v.checked_neg().ok_or_else(too_large)?), + Float(v) => Float(-v), + Decimal(v) => Decimal(-v), + Length(v) => Length(-v), + Angle(v) => Angle(-v), + Ratio(v) => Ratio(-v), + Relative(v) => Relative(-v), + Fraction(v) => Fraction(-v), + Duration(v) => Duration(-v), + Datetime(_) => mismatch!("cannot apply unary '-' to {}", value), + v => mismatch!("cannot apply '-' to {}", v), + }) +} + +/// Compute the sum of two values. +pub fn add(lhs: Value, rhs: Value) -> HintedStrResult { + use Value::*; + Ok(match (lhs, rhs) { + (a, None) => a, + (None, b) => b, + + (Int(a), Int(b)) => Int(a.checked_add(b).ok_or_else(too_large)?), + (Int(a), Float(b)) => Float(a as f64 + b), + (Float(a), Int(b)) => Float(a + b as f64), + (Float(a), Float(b)) => Float(a + b), + + (Decimal(a), Decimal(b)) => Decimal(a.checked_add(b).ok_or_else(too_large)?), + (Decimal(a), Int(b)) => Decimal( + a.checked_add(crate::foundations::Decimal::from(b)) + .ok_or_else(too_large)?, + ), + (Int(a), Decimal(b)) => Decimal( + crate::foundations::Decimal::from(a) + .checked_add(b) + .ok_or_else(too_large)?, + ), + + (Angle(a), Angle(b)) => Angle(a + b), + + (Length(a), Length(b)) => Length(a + b), + (Length(a), Ratio(b)) => Relative(b + a), + (Length(a), Relative(b)) => Relative(b + a), + + (Ratio(a), Length(b)) => Relative(a + b), + (Ratio(a), Ratio(b)) => Ratio(a + b), + (Ratio(a), Relative(b)) => Relative(b + a), + + (Relative(a), Length(b)) => Relative(a + b), + (Relative(a), Ratio(b)) => Relative(a + b), + (Relative(a), Relative(b)) => Relative(a + b), + + (Fraction(a), Fraction(b)) => Fraction(a + b), + + (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Str(a), Str(b)) => Str(a + b), + (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), + (Bytes(a), Bytes(b)) => Bytes(a + b), + (Content(a), Content(b)) => Content(a + b), + (Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())), + (Content(a), Str(b)) => Content(a + TextElem::packed(b)), + (Str(a), Content(b)) => Content(TextElem::packed(a) + b), + (Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b), + + (Array(a), Array(b)) => Array(a + b), + (Dict(a), Dict(b)) => Dict(a + b), + + (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { + Stroke::from_pair(color, thickness).into_value() + } + (Gradient(gradient), Length(thickness)) + | (Length(thickness), Gradient(gradient)) => { + Stroke::from_pair(gradient, thickness).into_value() + } + (Pattern(pattern), Length(thickness)) | (Length(thickness), Pattern(pattern)) => { + Stroke::from_pair(pattern, thickness).into_value() + } + + (Duration(a), Duration(b)) => Duration(a + b), + (Datetime(a), Duration(b)) => Datetime(a + b), + (Duration(a), Datetime(b)) => Datetime(b + a), + + // Type compatibility. + (Type(a), Str(b)) => Str(format_str!("{a}{b}")), + (Str(a), Type(b)) => Str(format_str!("{a}{b}")), + + (Dyn(a), Dyn(b)) => { + // Alignments can be summed. + if let (Some(&a), Some(&b)) = + (a.downcast::(), b.downcast::()) + { + return Ok((a + b)?.into_value()); + } + + mismatch!("cannot add {} and {}", a, b); + } + + (a, b) => mismatch!("cannot add {} and {}", a, b), + }) +} + +/// Compute the difference of two values. +pub fn sub(lhs: Value, rhs: Value) -> HintedStrResult { + use Value::*; + Ok(match (lhs, rhs) { + (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?), + (Int(a), Float(b)) => Float(a as f64 - b), + (Float(a), Int(b)) => Float(a - b as f64), + (Float(a), Float(b)) => Float(a - b), + + (Decimal(a), Decimal(b)) => Decimal(a.checked_sub(b).ok_or_else(too_large)?), + (Decimal(a), Int(b)) => Decimal( + a.checked_sub(crate::foundations::Decimal::from(b)) + .ok_or_else(too_large)?, + ), + (Int(a), Decimal(b)) => Decimal( + crate::foundations::Decimal::from(a) + .checked_sub(b) + .ok_or_else(too_large)?, + ), + + (Angle(a), Angle(b)) => Angle(a - b), + + (Length(a), Length(b)) => Length(a - b), + (Length(a), Ratio(b)) => Relative(-b + a), + (Length(a), Relative(b)) => Relative(-b + a), + + (Ratio(a), Length(b)) => Relative(a + -b), + (Ratio(a), Ratio(b)) => Ratio(a - b), + (Ratio(a), Relative(b)) => Relative(-b + a), + + (Relative(a), Length(b)) => Relative(a + -b), + (Relative(a), Ratio(b)) => Relative(a + -b), + (Relative(a), Relative(b)) => Relative(a - b), + + (Fraction(a), Fraction(b)) => Fraction(a - b), + + (Duration(a), Duration(b)) => Duration(a - b), + (Datetime(a), Duration(b)) => Datetime(a - b), + (Datetime(a), Datetime(b)) => Duration((a - b)?), + + (a, b) => mismatch!("cannot subtract {1} from {0}", a, b), + }) +} + +/// Compute the product of two values. +pub fn mul(lhs: Value, rhs: Value) -> HintedStrResult { + use Value::*; + Ok(match (lhs, rhs) { + (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?), + (Int(a), Float(b)) => Float(a as f64 * b), + (Float(a), Int(b)) => Float(a * b as f64), + (Float(a), Float(b)) => Float(a * b), + + (Decimal(a), Decimal(b)) => Decimal(a.checked_mul(b).ok_or_else(too_large)?), + (Decimal(a), Int(b)) => Decimal( + a.checked_mul(crate::foundations::Decimal::from(b)) + .ok_or_else(too_large)?, + ), + (Int(a), Decimal(b)) => Decimal( + crate::foundations::Decimal::from(a) + .checked_mul(b) + .ok_or_else(too_large)?, + ), + + (Length(a), Int(b)) => Length(a * b as f64), + (Length(a), Float(b)) => Length(a * b), + (Length(a), Ratio(b)) => Length(a * b.get()), + (Int(a), Length(b)) => Length(b * a as f64), + (Float(a), Length(b)) => Length(b * a), + (Ratio(a), Length(b)) => Length(b * a.get()), + + (Angle(a), Int(b)) => Angle(a * b as f64), + (Angle(a), Float(b)) => Angle(a * b), + (Angle(a), Ratio(b)) => Angle(a * b.get()), + (Int(a), Angle(b)) => Angle(a as f64 * b), + (Float(a), Angle(b)) => Angle(a * b), + (Ratio(a), Angle(b)) => Angle(a.get() * b), + + (Ratio(a), Ratio(b)) => Ratio(a * b), + (Ratio(a), Int(b)) => Ratio(a * b as f64), + (Ratio(a), Float(b)) => Ratio(a * b), + (Int(a), Ratio(b)) => Ratio(a as f64 * b), + (Float(a), Ratio(b)) => Ratio(a * b), + + (Relative(a), Int(b)) => Relative(a * b as f64), + (Relative(a), Float(b)) => Relative(a * b), + (Relative(a), Ratio(b)) => Relative(a * b.get()), + (Int(a), Relative(b)) => Relative(a as f64 * b), + (Float(a), Relative(b)) => Relative(a * b), + (Ratio(a), Relative(b)) => Relative(a.get() * b), + + (Fraction(a), Int(b)) => Fraction(a * b as f64), + (Fraction(a), Float(b)) => Fraction(a * b), + (Fraction(a), Ratio(b)) => Fraction(a * b.get()), + (Int(a), Fraction(b)) => Fraction(a as f64 * b), + (Float(a), Fraction(b)) => Fraction(a * b), + (Ratio(a), Fraction(b)) => Fraction(a.get() * b), + + (Str(a), Int(b)) => Str(a.repeat(Value::Int(b).cast()?)?), + (Int(a), Str(b)) => Str(b.repeat(Value::Int(a).cast()?)?), + (Array(a), Int(b)) => Array(a.repeat(Value::Int(b).cast()?)?), + (Int(a), Array(b)) => Array(b.repeat(Value::Int(a).cast()?)?), + (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)), + (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)), + + (Int(a), Duration(b)) => Duration(b * (a as f64)), + (Float(a), Duration(b)) => Duration(b * a), + (Duration(a), Int(b)) => Duration(a * (b as f64)), + (Duration(a), Float(b)) => Duration(a * b), + + (a, b) => mismatch!("cannot multiply {} with {}", a, b), + }) +} + +/// Compute the quotient of two values. +pub fn div(lhs: Value, rhs: Value) -> HintedStrResult { + use Value::*; + if is_zero(&rhs) { + bail!("cannot divide by zero"); + } + + Ok(match (lhs, rhs) { + (Int(a), Int(b)) => Float(a as f64 / b as f64), + (Int(a), Float(b)) => Float(a as f64 / b), + (Float(a), Int(b)) => Float(a / b as f64), + (Float(a), Float(b)) => Float(a / b), + + (Decimal(a), Decimal(b)) => Decimal(a.checked_div(b).ok_or_else(too_large)?), + (Decimal(a), Int(b)) => Decimal( + a.checked_div(crate::foundations::Decimal::from(b)) + .ok_or_else(too_large)?, + ), + (Int(a), Decimal(b)) => Decimal( + crate::foundations::Decimal::from(a) + .checked_div(b) + .ok_or_else(too_large)?, + ), + + (Length(a), Int(b)) => Length(a / b as f64), + (Length(a), Float(b)) => Length(a / b), + (Length(a), Length(b)) => Float(try_div_length(a, b)?), + (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?), + + (Angle(a), Int(b)) => Angle(a / b as f64), + (Angle(a), Float(b)) => Angle(a / b), + (Angle(a), Angle(b)) => Float(a / b), + + (Ratio(a), Int(b)) => Ratio(a / b as f64), + (Ratio(a), Float(b)) => Ratio(a / b), + (Ratio(a), Ratio(b)) => Float(a / b), + (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel), + + (Relative(a), Int(b)) => Relative(a / b as f64), + (Relative(a), Float(b)) => Relative(a / b), + (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?), + (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b), + (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?), + + (Fraction(a), Int(b)) => Fraction(a / b as f64), + (Fraction(a), Float(b)) => Fraction(a / b), + (Fraction(a), Fraction(b)) => Float(a / b), + + (Duration(a), Int(b)) => Duration(a / (b as f64)), + (Duration(a), Float(b)) => Duration(a / b), + (Duration(a), Duration(b)) => Float(a / b), + + (a, b) => mismatch!("cannot divide {} by {}", a, b), + }) +} + +/// Whether a value is a numeric zero. +fn is_zero(v: &Value) -> bool { + use Value::*; + match *v { + Int(v) => v == 0, + Float(v) => v == 0.0, + Decimal(v) => v.is_zero(), + Length(v) => v.is_zero(), + Angle(v) => v.is_zero(), + Ratio(v) => v.is_zero(), + Relative(v) => v.is_zero(), + Fraction(v) => v.is_zero(), + Duration(v) => v.is_zero(), + _ => false, + } +} + +/// Try to divide two lengths. +fn try_div_length(a: Length, b: Length) -> StrResult { + a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into()) +} + +/// Try to divide two relative lengths. +fn try_div_relative(a: Rel, b: Rel) -> StrResult { + a.try_div(b) + .ok_or_else(|| "cannot divide these two relative lengths".into()) +} + +/// Compute the logical "not" of a value. +pub fn not(value: Value) -> HintedStrResult { + match value { + Value::Bool(b) => Ok(Value::Bool(!b)), + v => mismatch!("cannot apply 'not' to {}", v), + } +} + +/// Compute the logical "and" of two values. +pub fn and(lhs: Value, rhs: Value) -> HintedStrResult { + match (lhs, rhs) { + (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)), + (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), + } +} + +/// Compute the logical "or" of two values. +pub fn or(lhs: Value, rhs: Value) -> HintedStrResult { + match (lhs, rhs) { + (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)), + (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), + } +} + +/// Compute whether two values are equal. +pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult { + Ok(Value::Bool(equal(&lhs, &rhs))) +} + +/// Compute whether two values are unequal. +pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult { + Ok(Value::Bool(!equal(&lhs, &rhs))) +} + +macro_rules! comparison { + ($name:ident, $op:tt, $($pat:tt)*) => { + /// Compute how a value compares with another value. + pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult { + let ordering = compare(&lhs, &rhs)?; + Ok(Value::Bool(matches!(ordering, $($pat)*))) + } + }; +} + +comparison!(lt, "<", Ordering::Less); +comparison!(leq, "<=", Ordering::Less | Ordering::Equal); +comparison!(gt, ">", Ordering::Greater); +comparison!(geq, ">=", Ordering::Greater | Ordering::Equal); + +/// Determine whether two values are equal. +pub fn equal(lhs: &Value, rhs: &Value) -> bool { + use Value::*; + match (lhs, rhs) { + // Compare reflexively. + (None, None) => true, + (Auto, Auto) => true, + (Bool(a), Bool(b)) => a == b, + (Int(a), Int(b)) => a == b, + (Float(a), Float(b)) => a == b, + (Decimal(a), Decimal(b)) => a == b, + (Length(a), Length(b)) => a == b, + (Angle(a), Angle(b)) => a == b, + (Ratio(a), Ratio(b)) => a == b, + (Relative(a), Relative(b)) => a == b, + (Fraction(a), Fraction(b)) => a == b, + (Color(a), Color(b)) => a == b, + (Symbol(a), Symbol(b)) => a == b, + (Version(a), Version(b)) => a == b, + (Str(a), Str(b)) => a == b, + (Bytes(a), Bytes(b)) => a == b, + (Label(a), Label(b)) => a == b, + (Content(a), Content(b)) => a == b, + (Array(a), Array(b)) => a == b, + (Dict(a), Dict(b)) => a == b, + (Func(a), Func(b)) => a == b, + (Args(a), Args(b)) => a == b, + (Type(a), Type(b)) => a == b, + (Module(a), Module(b)) => a == b, + (Plugin(a), Plugin(b)) => a == b, + (Datetime(a), Datetime(b)) => a == b, + (Duration(a), Duration(b)) => a == b, + (Dyn(a), Dyn(b)) => a == b, + + // Some technically different things should compare equal. + (&Int(i), &Float(f)) | (&Float(f), &Int(i)) => i as f64 == f, + (&Int(i), &Decimal(d)) | (&Decimal(d), &Int(i)) => { + crate::foundations::Decimal::from(i) == d + } + (&Length(len), &Relative(rel)) | (&Relative(rel), &Length(len)) => { + len == rel.abs && rel.rel.is_zero() + } + (&Ratio(rat), &Relative(rel)) | (&Relative(rel), &Ratio(rat)) => { + rat == rel.rel && rel.abs.is_zero() + } + + // Type compatibility. + (Type(ty), Str(str)) | (Str(str), Type(ty)) => ty.compat_name() == str.as_str(), + + _ => false, + } +} + +/// Compare two values. +pub fn compare(lhs: &Value, rhs: &Value) -> StrResult { + use Value::*; + Ok(match (lhs, rhs) { + (Bool(a), Bool(b)) => a.cmp(b), + (Int(a), Int(b)) => a.cmp(b), + (Float(a), Float(b)) => try_cmp_values(a, b)?, + (Decimal(a), Decimal(b)) => a.cmp(b), + (Length(a), Length(b)) => try_cmp_values(a, b)?, + (Angle(a), Angle(b)) => a.cmp(b), + (Ratio(a), Ratio(b)) => a.cmp(b), + (Relative(a), Relative(b)) => try_cmp_values(a, b)?, + (Fraction(a), Fraction(b)) => a.cmp(b), + (Version(a), Version(b)) => a.cmp(b), + (Str(a), Str(b)) => a.cmp(b), + + // Some technically different things should be comparable. + (Int(a), Float(b)) => try_cmp_values(&(*a as f64), b)?, + (Float(a), Int(b)) => try_cmp_values(a, &(*b as f64))?, + (Int(a), Decimal(b)) => crate::foundations::Decimal::from(*a).cmp(b), + (Decimal(a), Int(b)) => a.cmp(&crate::foundations::Decimal::from(*b)), + (Length(a), Relative(b)) if b.rel.is_zero() => try_cmp_values(a, &b.abs)?, + (Ratio(a), Relative(b)) if b.abs.is_zero() => a.cmp(&b.rel), + (Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?, + (Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b), + + (Duration(a), Duration(b)) => a.cmp(b), + (Datetime(a), Datetime(b)) => try_cmp_datetimes(a, b)?, + (Array(a), Array(b)) => try_cmp_arrays(a.as_slice(), b.as_slice())?, + + _ => mismatch!("cannot compare {} and {}", lhs, rhs), + }) +} + +/// Try to compare two values. +fn try_cmp_values(a: &T, b: &T) -> StrResult { + a.partial_cmp(b) + .ok_or_else(|| eco_format!("cannot compare {} with {}", a.repr(), b.repr())) +} + +/// Try to compare two datetimes. +fn try_cmp_datetimes(a: &Datetime, b: &Datetime) -> StrResult { + a.partial_cmp(b) + .ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind())) +} + +/// Try to compare arrays of values lexicographically. +fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult { + a.iter() + .zip(b.iter()) + .find_map(|(first, second)| { + match compare(first, second) { + // Keep searching for a pair of elements that isn't equal. + Ok(Ordering::Equal) => None, + // Found a pair which either is not equal or not comparable, so + // we stop searching. + result => Some(result), + } + }) + .unwrap_or_else(|| { + // The two arrays are equal up to the shortest array's extent, + // so compare their lengths instead. + Ok(a.len().cmp(&b.len())) + }) +} + +/// Test whether one value is "in" another one. +pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult { + if let Some(b) = contains(&lhs, &rhs) { + Ok(Value::Bool(b)) + } else { + mismatch!("cannot apply 'in' to {} and {}", lhs, rhs) + } +} + +/// Test whether one value is "not in" another one. +pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult { + if let Some(b) = contains(&lhs, &rhs) { + Ok(Value::Bool(!b)) + } else { + mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs) + } +} + +/// Test for containment. +pub fn contains(lhs: &Value, rhs: &Value) -> Option { + use Value::*; + match (lhs, rhs) { + (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), + (Dyn(a), Str(b)) => a.downcast::().map(|regex| regex.is_match(b)), + (Str(a), Dict(b)) => Some(b.contains(a)), + (a, Array(b)) => Some(b.contains(a.clone())), + + // Type compatibility. + (Type(a), Str(b)) => Some(b.as_str().contains(a.compat_name())), + (Type(a), Dict(b)) => Some(b.contains(a.compat_name())), + + _ => Option::None, + } +} + +#[cold] +fn too_large() -> &'static str { + "value is too large" +} -- cgit v1.2.3