summaryrefslogtreecommitdiff
path: root/src/model/value.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/model/value.rs')
-rw-r--r--src/model/value.rs497
1 files changed, 0 insertions, 497 deletions
diff --git a/src/model/value.rs b/src/model/value.rs
deleted file mode 100644
index f6ab95de..00000000
--- a/src/model/value.rs
+++ /dev/null
@@ -1,497 +0,0 @@
-use std::any::Any;
-use std::cmp::Ordering;
-use std::fmt::{self, Debug, Formatter};
-use std::hash::{Hash, Hasher};
-use std::sync::Arc;
-
-use ecow::{eco_format, EcoString};
-use siphasher::sip128::{Hasher128, SipHasher};
-
-use super::{
- format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module,
- Str, Symbol,
-};
-use crate::diag::StrResult;
-use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
-use crate::syntax::{ast, Span};
-
-/// A computational value.
-#[derive(Clone)]
-pub enum Value {
- /// The value that indicates the absence of a meaningful value.
- None,
- /// A value that indicates some smart default behaviour.
- Auto,
- /// A boolean: `true, false`.
- Bool(bool),
- /// An integer: `120`.
- Int(i64),
- /// A floating-point number: `1.2`, `10e-4`.
- Float(f64),
- /// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`.
- Length(Length),
- /// An angle: `1.5rad`, `90deg`.
- Angle(Angle),
- /// A ratio: `50%`.
- Ratio(Ratio),
- /// A relative length, combination of a ratio and a length: `20% + 5cm`.
- Relative(Rel<Length>),
- /// A fraction: `1fr`.
- Fraction(Fr),
- /// A color value: `#f79143ff`.
- Color(Color),
- /// A symbol: `arrow.l`.
- Symbol(Symbol),
- /// A string: `"string"`.
- Str(Str),
- /// A label: `<intro>`.
- Label(Label),
- /// A content value: `[*Hi* there]`.
- Content(Content),
- /// An array of values: `(1, "hi", 12cm)`.
- Array(Array),
- /// A dictionary value: `(color: #f79143, pattern: dashed)`.
- Dict(Dict),
- /// An executable function.
- Func(Func),
- /// Captured arguments to a function.
- Args(Args),
- /// A module.
- Module(Module),
- /// A dynamic value.
- Dyn(Dynamic),
-}
-
-impl Value {
- /// Create a new dynamic value.
- pub fn dynamic<T>(any: T) -> Self
- where
- T: Type + Debug + PartialEq + Hash + Sync + Send + 'static,
- {
- Self::Dyn(Dynamic::new(any))
- }
-
- /// Create a numeric value from a number with a unit.
- pub fn numeric(pair: (f64, ast::Unit)) -> Self {
- let (v, unit) = pair;
- match unit {
- ast::Unit::Length(unit) => Abs::with_unit(v, unit).into(),
- ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
- ast::Unit::Em => Em::new(v).into(),
- ast::Unit::Fr => Fr::new(v).into(),
- ast::Unit::Percent => Ratio::new(v / 100.0).into(),
- }
- }
-
- /// The name of the stored value's type.
- pub fn type_name(&self) -> &'static str {
- match self {
- Self::None => "none",
- Self::Auto => "auto",
- Self::Bool(_) => bool::TYPE_NAME,
- Self::Int(_) => i64::TYPE_NAME,
- Self::Float(_) => f64::TYPE_NAME,
- Self::Length(_) => Length::TYPE_NAME,
- Self::Angle(_) => Angle::TYPE_NAME,
- Self::Ratio(_) => Ratio::TYPE_NAME,
- Self::Relative(_) => Rel::<Length>::TYPE_NAME,
- Self::Fraction(_) => Fr::TYPE_NAME,
- Self::Color(_) => Color::TYPE_NAME,
- Self::Symbol(_) => Symbol::TYPE_NAME,
- Self::Str(_) => Str::TYPE_NAME,
- Self::Label(_) => Label::TYPE_NAME,
- Self::Content(_) => Content::TYPE_NAME,
- Self::Array(_) => Array::TYPE_NAME,
- Self::Dict(_) => Dict::TYPE_NAME,
- Self::Func(_) => Func::TYPE_NAME,
- Self::Args(_) => Args::TYPE_NAME,
- Self::Module(_) => Module::TYPE_NAME,
- Self::Dyn(v) => v.type_name(),
- }
- }
-
- /// Try to cast the value into a specific type.
- pub fn cast<T: Cast>(self) -> StrResult<T> {
- T::cast(self)
- }
-
- /// Try to access a field on the value.
- pub fn field(&self, field: &str) -> StrResult<Value> {
- match self {
- Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol),
- Self::Dict(dict) => dict.at(&field).cloned(),
- Self::Content(content) => content
- .field(&field)
- .ok_or_else(|| eco_format!("unknown field `{field}`")),
- Self::Module(module) => module.get(&field).cloned(),
- v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
- }
- }
-
- /// Return the debug representation of the value.
- pub fn repr(&self) -> Str {
- format_str!("{:?}", self)
- }
-
- /// Attach a span to the value, if possible.
- pub fn spanned(self, span: Span) -> Self {
- match self {
- Value::Content(v) => Value::Content(v.spanned(span)),
- Value::Func(v) => Value::Func(v.spanned(span)),
- v => v,
- }
- }
-
- /// Return the display representation of the value.
- pub fn display(self) -> Content {
- match self {
- Self::None => Content::empty(),
- Self::Int(v) => item!(text)(eco_format!("{}", v)),
- Self::Float(v) => item!(text)(eco_format!("{}", v)),
- Self::Str(v) => item!(text)(v.into()),
- Self::Symbol(v) => item!(text)(v.get().into()),
- Self::Content(v) => v,
- Self::Func(_) => Content::empty(),
- Self::Module(module) => module.content(),
- _ => item!(raw)(self.repr().into(), Some("typc".into()), false),
- }
- }
-
- /// Try to extract documentation for the value.
- pub fn docs(&self) -> Option<&'static str> {
- match self {
- Self::Func(func) => func.info().map(|info| info.docs),
- _ => None,
- }
- }
-}
-
-impl Default for Value {
- fn default() -> Self {
- Value::None
- }
-}
-
-impl Debug for Value {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::None => f.pad("none"),
- Self::Auto => f.pad("auto"),
- Self::Bool(v) => Debug::fmt(v, f),
- Self::Int(v) => Debug::fmt(v, f),
- Self::Float(v) => Debug::fmt(v, f),
- Self::Length(v) => Debug::fmt(v, f),
- Self::Angle(v) => Debug::fmt(v, f),
- Self::Ratio(v) => Debug::fmt(v, f),
- Self::Relative(v) => Debug::fmt(v, f),
- Self::Fraction(v) => Debug::fmt(v, f),
- Self::Color(v) => Debug::fmt(v, f),
- Self::Symbol(v) => Debug::fmt(v, f),
- Self::Str(v) => Debug::fmt(v, f),
- Self::Label(v) => Debug::fmt(v, f),
- Self::Content(_) => f.pad("[...]"),
- Self::Array(v) => Debug::fmt(v, f),
- Self::Dict(v) => Debug::fmt(v, f),
- Self::Func(v) => Debug::fmt(v, f),
- Self::Args(v) => Debug::fmt(v, f),
- Self::Module(v) => Debug::fmt(v, f),
- Self::Dyn(v) => Debug::fmt(v, f),
- }
- }
-}
-
-impl PartialEq for Value {
- fn eq(&self, other: &Self) -> bool {
- ops::equal(self, other)
- }
-}
-
-impl PartialOrd for Value {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- ops::compare(self, other)
- }
-}
-
-impl Hash for Value {
- fn hash<H: Hasher>(&self, state: &mut H) {
- std::mem::discriminant(self).hash(state);
- match self {
- Self::None => {}
- Self::Auto => {}
- Self::Bool(v) => v.hash(state),
- Self::Int(v) => v.hash(state),
- Self::Float(v) => v.to_bits().hash(state),
- Self::Length(v) => v.hash(state),
- Self::Angle(v) => v.hash(state),
- Self::Ratio(v) => v.hash(state),
- Self::Relative(v) => v.hash(state),
- Self::Fraction(v) => v.hash(state),
- Self::Color(v) => v.hash(state),
- Self::Symbol(v) => v.hash(state),
- Self::Str(v) => v.hash(state),
- Self::Label(v) => v.hash(state),
- Self::Content(v) => v.hash(state),
- Self::Array(v) => v.hash(state),
- Self::Dict(v) => v.hash(state),
- Self::Func(v) => v.hash(state),
- Self::Args(v) => v.hash(state),
- Self::Module(v) => v.hash(state),
- Self::Dyn(v) => v.hash(state),
- }
- }
-}
-
-impl From<i32> for Value {
- fn from(v: i32) -> Self {
- Self::Int(v as i64)
- }
-}
-
-impl From<usize> for Value {
- fn from(v: usize) -> Self {
- Self::Int(v as i64)
- }
-}
-
-impl From<Abs> for Value {
- fn from(v: Abs) -> Self {
- Self::Length(v.into())
- }
-}
-
-impl From<Em> for Value {
- fn from(v: Em) -> Self {
- Self::Length(v.into())
- }
-}
-
-impl From<RgbaColor> for Value {
- fn from(v: RgbaColor) -> Self {
- Self::Color(v.into())
- }
-}
-
-impl From<&str> for Value {
- fn from(v: &str) -> Self {
- Self::Str(v.into())
- }
-}
-
-impl From<EcoString> for Value {
- fn from(v: EcoString) -> Self {
- Self::Str(v.into())
- }
-}
-
-impl From<String> for Value {
- fn from(v: String) -> Self {
- Self::Str(v.into())
- }
-}
-
-impl From<Dynamic> for Value {
- fn from(v: Dynamic) -> Self {
- Self::Dyn(v)
- }
-}
-
-/// A dynamic value.
-#[derive(Clone, Hash)]
-pub struct Dynamic(Arc<dyn Bounds>);
-
-impl Dynamic {
- /// Create a new instance from any value that satisifies the required bounds.
- pub fn new<T>(any: T) -> Self
- where
- T: Type + Debug + PartialEq + Hash + Sync + Send + 'static,
- {
- Self(Arc::new(any))
- }
-
- /// Whether the wrapped type is `T`.
- pub fn is<T: Type + 'static>(&self) -> bool {
- (*self.0).as_any().is::<T>()
- }
-
- /// Try to downcast to a reference to a specific type.
- pub fn downcast<T: Type + 'static>(&self) -> Option<&T> {
- (*self.0).as_any().downcast_ref()
- }
-
- /// The name of the stored value's type.
- pub fn type_name(&self) -> &'static str {
- self.0.dyn_type_name()
- }
-}
-
-impl Debug for Dynamic {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- Debug::fmt(&self.0, f)
- }
-}
-
-impl PartialEq for Dynamic {
- fn eq(&self, other: &Self) -> bool {
- self.0.dyn_eq(other)
- }
-}
-
-trait Bounds: Debug + Sync + Send + 'static {
- fn as_any(&self) -> &dyn Any;
- fn dyn_eq(&self, other: &Dynamic) -> bool;
- fn dyn_type_name(&self) -> &'static str;
- fn hash128(&self) -> u128;
-}
-
-impl<T> Bounds for T
-where
- T: Type + Debug + PartialEq + Hash + Sync + Send + 'static,
-{
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn dyn_eq(&self, other: &Dynamic) -> bool {
- let Some(other) = other.downcast::<Self>() else { return false };
- self == other
- }
-
- fn dyn_type_name(&self) -> &'static str {
- T::TYPE_NAME
- }
-
- fn hash128(&self) -> u128 {
- // Also hash the TypeId since values with different types but
- // equal data should be different.
- let mut state = SipHasher::new();
- self.type_id().hash(&mut state);
- self.hash(&mut state);
- state.finish128().as_u128()
- }
-}
-
-impl Hash for dyn Bounds {
- fn hash<H: Hasher>(&self, state: &mut H) {
- state.write_u128(self.hash128());
- }
-}
-
-/// The type of a value.
-pub trait Type {
- /// The name of the type.
- const TYPE_NAME: &'static str;
-}
-
-/// Implement traits for primitives.
-macro_rules! primitive {
- (
- $type:ty: $name:literal, $variant:ident
- $(, $other:ident$(($binding:ident))? => $out:expr)*
- ) => {
- impl Type for $type {
- const TYPE_NAME: &'static str = $name;
- }
-
- impl Cast for $type {
- fn is(value: &Value) -> bool {
- matches!(value, Value::$variant(_)
- $(| primitive!(@$other $(($binding))?))*)
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::$variant(v) => Ok(v),
- $(Value::$other$(($binding))? => Ok($out),)*
- v => Err(eco_format!(
- "expected {}, found {}",
- Self::TYPE_NAME,
- v.type_name(),
- )),
- }
- }
-
- fn describe() -> CastInfo {
- CastInfo::Type(Self::TYPE_NAME)
- }
- }
-
- impl From<$type> for Value {
- fn from(v: $type) -> Self {
- Value::$variant(v)
- }
- }
- };
-
- (@$other:ident($binding:ident)) => { Value::$other(_) };
- (@$other:ident) => { Value::$other };
-}
-
-primitive! { bool: "boolean", Bool }
-primitive! { i64: "integer", Int }
-primitive! { f64: "float", Float, Int(v) => v as f64 }
-primitive! { Length: "length", Length }
-primitive! { Angle: "angle", Angle }
-primitive! { Ratio: "ratio", Ratio }
-primitive! { Rel<Length>: "relative length",
- Relative,
- Length(v) => v.into(),
- Ratio(v) => v.into()
-}
-primitive! { Fr: "fraction", Fraction }
-primitive! { Color: "color", Color }
-primitive! { Symbol: "symbol", Symbol }
-primitive! {
- Str: "string",
- Str,
- Symbol(symbol) => symbol.get().into()
-}
-primitive! { Label: "label", Label }
-primitive! { Content: "content",
- Content,
- None => Content::empty(),
- Symbol(v) => item!(text)(v.get().into()),
- Str(v) => item!(text)(v.into())
-}
-primitive! { Array: "array", Array }
-primitive! { Dict: "dictionary", Dict }
-primitive! { Func: "function", Func }
-primitive! { Module: "module", Module }
-primitive! { Args: "arguments", Args }
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::model::{array, dict};
-
- #[track_caller]
- fn test(value: impl Into<Value>, exp: &str) {
- assert_eq!(format!("{:?}", value.into()), exp);
- }
-
- #[test]
- fn test_value_debug() {
- // Primitives.
- test(Value::None, "none");
- test(false, "false");
- test(12i64, "12");
- test(3.14, "3.14");
- test(Abs::pt(5.5), "5.5pt");
- test(Angle::deg(90.0), "90deg");
- test(Ratio::one() / 2.0, "50%");
- test(Ratio::new(0.3) + Length::from(Abs::cm(2.0)), "30% + 56.69pt");
- test(Fr::one() * 7.55, "7.55fr");
- test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "rgb(\"#010101\")");
-
- // Collections.
- test("hello", r#""hello""#);
- test("\n", r#""\n""#);
- test("\\", r#""\\""#);
- test("\"", r#""\"""#);
- test(array![], "()");
- test(array![Value::None], "(none,)");
- test(array![1, 2], "(1, 2)");
- test(dict![], "(:)");
- test(dict!["one" => 1], "(one: 1)");
- test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
- }
-}