diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-31 12:59:53 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-31 12:59:53 +0200 |
| commit | 3481d8cc81a2b3a14118869c7f0ffe204ff3efc8 (patch) | |
| tree | 907efa2e092366a24e25243854b1a4e088cc04a9 /src | |
| parent | ee84bf74083f5b9cc88a2a0a968dc905b1eef22c (diff) | |
More utility functions
- join("a", "b", "c", sep: ", ")
- int("12")
- float("31.4e-1")
- str(10)
- sorted((3, 2, 1))
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/array.rs | 21 | ||||
| -rw-r--r-- | src/eval/mod.rs | 3 | ||||
| -rw-r--r-- | src/eval/ops.rs | 13 | ||||
| -rw-r--r-- | src/eval/str.rs | 23 | ||||
| -rw-r--r-- | src/eval/value.rs | 5 | ||||
| -rw-r--r-- | src/library/mod.rs | 13 | ||||
| -rw-r--r-- | src/library/utility.rs | 113 |
7 files changed, 161 insertions, 30 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs index acf44ab2..bae89c4b 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::iter::FromIterator; @@ -80,6 +81,26 @@ impl Array { self.0.iter() } + /// Return a sorted version of this array. + /// + /// Returns an error if two values could not be compared. + pub fn sorted(mut self) -> StrResult<Self> { + let mut result = Ok(()); + Rc::make_mut(&mut self.0).sort_by(|a, b| { + a.partial_cmp(b).unwrap_or_else(|| { + if result.is_ok() { + result = Err(format!( + "cannot compare {} with {}", + a.type_name(), + b.type_name(), + )); + } + Ordering::Equal + }) + }); + result.map(|_| self) + } + /// Repeat this array `n` times. pub fn repeat(&self, n: i64) -> StrResult<Self> { let count = usize::try_from(n) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index b8561a87..f48312c8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -5,13 +5,14 @@ mod array; #[macro_use] mod dict; #[macro_use] +mod str; +#[macro_use] mod value; mod capture; mod function; mod ops; mod scope; mod state; -mod str; mod template; mod walk; diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 6d34b877..c7a45614 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -291,12 +291,21 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> { match (lhs, rhs) { (Bool(a), Bool(b)) => a.partial_cmp(b), (Int(a), Int(b)) => a.partial_cmp(b), - (Int(a), Float(b)) => (*a as f64).partial_cmp(b), - (Float(a), Int(b)) => a.partial_cmp(&(*b as f64)), (Float(a), Float(b)) => a.partial_cmp(b), (Angle(a), Angle(b)) => a.partial_cmp(b), (Length(a), Length(b)) => a.partial_cmp(b), + (Relative(a), Relative(b)) => a.partial_cmp(b), + (Fractional(a), Fractional(b)) => a.partial_cmp(b), (Str(a), Str(b)) => a.partial_cmp(b), + + // Some technically different things should be comparable. + (&Int(a), &Float(b)) => (a as f64).partial_cmp(&b), + (&Float(a), &Int(b)) => a.partial_cmp(&(b as f64)), + (&Length(a), &Linear(b)) if b.rel.is_zero() => a.partial_cmp(&b.abs), + (&Relative(a), &Linear(b)) if b.abs.is_zero() => a.partial_cmp(&b.rel), + (&Linear(a), &Length(b)) if a.rel.is_zero() => a.abs.partial_cmp(&b), + (&Linear(a), &Relative(b)) if a.abs.is_zero() => a.rel.partial_cmp(&b), + _ => Option::None, } } diff --git a/src/eval/str.rs b/src/eval/str.rs index a358cd9f..099a4363 100644 --- a/src/eval/str.rs +++ b/src/eval/str.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::ops::{Add, AddAssign, Deref}; @@ -5,6 +6,16 @@ use std::ops::{Add, AddAssign, Deref}; use crate::diag::StrResult; use crate::util::EcoString; +/// Create a new [`Str`] from a format string. +macro_rules! format_str { + ($($tts:tt)*) => {{ + use std::fmt::Write; + let mut s = $crate::util::EcoString::new(); + write!(s, $($tts)*).unwrap(); + $crate::eval::Str::from(s) + }}; +} + /// A string value with inline storage and clone-on-write semantics. #[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct Str(EcoString); @@ -92,6 +103,18 @@ impl AddAssign for Str { } } +impl AsRef<str> for Str { + fn as_ref(&self) -> &str { + self + } +} + +impl Borrow<str> for Str { + fn borrow(&self) -> &str { + self + } +} + impl From<char> for Str { fn from(c: char) -> Self { Self(c.into()) diff --git a/src/eval/value.rs b/src/eval/value.rs index 5edf0362..77cb766c 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -88,6 +88,11 @@ impl Value { { T::cast(self) } + + /// Join the value with another value. + pub fn join(self, rhs: Self) -> StrResult<Self> { + ops::join(self, rhs) + } } impl Default for Value { diff --git a/src/library/mod.rs b/src/library/mod.rs index ca99d43b..102291cc 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -17,8 +17,8 @@ use std::convert::TryFrom; use std::rc::Rc; use crate::color::{Color, RgbaColor}; -use crate::diag::TypResult; -use crate::eval::{Arguments, EvalContext, Scope, State, Str, Template, Value}; +use crate::diag::{At, TypResult}; +use crate::eval::{Arguments, Array, EvalContext, Scope, State, Str, Template, Value}; use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use crate::geom::*; use crate::layout::LayoutNode; @@ -59,13 +59,18 @@ pub fn new() -> Scope { // Utility. std.def_func("type", type_); std.def_func("repr", repr); - std.def_func("len", len); - std.def_func("rgb", rgb); + std.def_func("join", join); + std.def_func("int", int); + std.def_func("float", float); + std.def_func("str", str); std.def_func("abs", abs); std.def_func("min", min); std.def_func("max", max); + std.def_func("rgb", rgb); std.def_func("lower", lower); std.def_func("upper", upper); + std.def_func("len", len); + std.def_func("sorted", sorted); // Colors. std.def_const("white", RgbaColor::WHITE); diff --git a/src/library/utility.rs b/src/library/utility.rs index 0ece88ac..19dfc3ec 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -15,34 +15,65 @@ pub fn repr(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { Ok(args.expect::<Value>("value")?.to_string().into()) } -/// `len`: The length of a string, an array or a dictionary. -pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - let Spanned { v, span } = args.expect("collection")?; +/// `join`: Join a sequence of values, optionally interspersing it with another +/// value. +pub fn join(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let span = args.span; + let sep = args.named::<Value>("sep")?.unwrap_or(Value::None); + + let mut result = Value::None; + let mut iter = args.all::<Value>(); + + if let Some(first) = iter.next() { + result = first; + } + + for value in iter { + result = result.join(sep.clone()).at(span)?; + result = result.join(value).at(span)?; + } + + Ok(result) +} + +/// `int`: Try to convert a value to a integer. +pub fn int(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; Ok(Value::Int(match v { - Value::Str(v) => v.len(), - Value::Array(v) => v.len(), - Value::Dict(v) => v.len(), - _ => bail!(span, "expected string, array or dictionary"), + Value::Bool(v) => v as i64, + Value::Int(v) => v, + Value::Float(v) => v as i64, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid integer"), + }, + v => bail!(span, "cannot convert {} to integer", v.type_name()), })) } -/// `rgb`: Create an RGB(A) color. -pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - Ok(Value::Color(Color::Rgba( - if let Some(string) = args.eat::<Spanned<Str>>() { - match RgbaColor::from_str(&string.v) { - Ok(color) => color, - Err(_) => bail!(string.span, "invalid color"), - } - } else { - let r = args.expect("red component")?; - let g = args.expect("green component")?; - let b = args.expect("blue component")?; - let a = args.eat().unwrap_or(1.0); - let f = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8; - RgbaColor::new(f(r), f(g), f(b), f(a)) +/// `float`: Try to convert a value to a float. +pub fn float(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Float(match v { + Value::Int(v) => v as f64, + Value::Float(v) => v, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid float"), }, - ))) + v => bail!(span, "cannot convert {} to float", v.type_name()), + })) +} + +/// `str`: Try to convert a value to a string. +pub fn str(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Str(match v { + Value::Int(v) => format_str!("{}", v), + Value::Float(v) => format_str!("{}", v), + Value::Str(v) => v, + v => bail!(span, "cannot convert {} to string", v.type_name()), + })) } /// `abs`: The absolute value of a numeric value. @@ -91,6 +122,25 @@ fn minmax(args: &mut Arguments, goal: Ordering) -> TypResult<Value> { Ok(extremum) } +/// `rgb`: Create an RGB(A) color. +pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + Ok(Value::Color(Color::Rgba( + if let Some(string) = args.eat::<Spanned<Str>>() { + match RgbaColor::from_str(&string.v) { + Ok(color) => color, + Err(_) => bail!(string.span, "invalid color"), + } + } else { + let r = args.expect("red component")?; + let g = args.expect("green component")?; + let b = args.expect("blue component")?; + let a = args.eat().unwrap_or(1.0); + let f = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8; + RgbaColor::new(f(r), f(g), f(b), f(a)) + }, + ))) +} + /// `lower`: Convert a string to lowercase. pub fn lower(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { Ok(args.expect::<Str>("string")?.to_lowercase().into()) @@ -100,3 +150,20 @@ pub fn lower(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn upper(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { Ok(args.expect::<Str>("string")?.to_uppercase().into()) } + +/// `len`: The length of a string, an array or a dictionary. +pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let Spanned { v, span } = args.expect("collection")?; + Ok(Value::Int(match v { + Value::Str(v) => v.len(), + Value::Array(v) => v.len(), + Value::Dict(v) => v.len(), + _ => bail!(span, "expected string, array or dictionary"), + })) +} + +/// `sorted`: The sorted version of an array. +pub fn sorted(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?; + Ok(Value::Array(v.sorted().at(span)?)) +} |
