summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-31 12:59:53 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-31 12:59:53 +0200
commit3481d8cc81a2b3a14118869c7f0ffe204ff3efc8 (patch)
tree907efa2e092366a24e25243854b1a4e088cc04a9 /src
parentee84bf74083f5b9cc88a2a0a968dc905b1eef22c (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.rs21
-rw-r--r--src/eval/mod.rs3
-rw-r--r--src/eval/ops.rs13
-rw-r--r--src/eval/str.rs23
-rw-r--r--src/eval/value.rs5
-rw-r--r--src/library/mod.rs13
-rw-r--r--src/library/utility.rs113
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)?))
+}