summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-06-06 21:13:59 +0200
committerLaurenz <laurmaedje@gmail.com>2023-06-06 22:06:16 +0200
commitfd417da04f7ca4b995de7f6510abafd3e9c31307 (patch)
tree3675529c75ca7363701ac8ea306de2cc1d3cbcb3 /src/eval
parent168bdf35bd773e67343c965cb473492cc5cae9e7 (diff)
Improve value casting infrastructure
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs43
-rw-r--r--src/eval/array.rs95
-rw-r--r--src/eval/auto.rs39
-rw-r--r--src/eval/cast.rs367
-rw-r--r--src/eval/datetime.rs6
-rw-r--r--src/eval/dict.rs19
-rw-r--r--src/eval/func.rs36
-rw-r--r--src/eval/int.rs81
-rw-r--r--src/eval/methods.rs153
-rw-r--r--src/eval/mod.rs71
-rw-r--r--src/eval/none.rs74
-rw-r--r--src/eval/ops.rs4
-rw-r--r--src/eval/scope.rs15
-rw-r--r--src/eval/str.rs80
-rw-r--r--src/eval/symbol.rs3
-rw-r--r--src/eval/value.rs63
16 files changed, 658 insertions, 491 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
index bea2baa1..da29eeaf 100644
--- a/src/eval/args.rs
+++ b/src/eval/args.rs
@@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use ecow::{eco_format, EcoVec};
-use super::{Array, Cast, Dict, Str, Value};
+use super::{Array, Dict, FromValue, IntoValue, Str, Value};
use crate::diag::{bail, At, SourceResult};
use crate::syntax::{Span, Spanned};
use crate::util::pretty_array_like;
@@ -29,10 +29,14 @@ pub struct Arg {
impl Args {
/// Create positional arguments from a span and values.
- pub fn new(span: Span, values: impl IntoIterator<Item = Value>) -> Self {
+ pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self {
let items = values
.into_iter()
- .map(|value| Arg { span, name: None, value: Spanned::new(value, span) })
+ .map(|value| Arg {
+ span,
+ name: None,
+ value: Spanned::new(value.into_value(), span),
+ })
.collect();
Self { span, items }
}
@@ -49,13 +53,13 @@ impl Args {
/// Consume and cast the first positional argument if there is one.
pub fn eat<T>(&mut self) -> SourceResult<Option<T>>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
for (i, slot) in self.items.iter().enumerate() {
if slot.name.is_none() {
let value = self.items.remove(i).value;
let span = value.span;
- return T::cast(value).at(span).map(Some);
+ return T::from_value(value).at(span).map(Some);
}
}
Ok(None)
@@ -87,24 +91,24 @@ impl Args {
/// left.
pub fn expect<T>(&mut self, what: &str) -> SourceResult<T>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
match self.eat()? {
Some(v) => Ok(v),
- None => bail!(self.span, "missing argument: {}", what),
+ None => bail!(self.span, "missing argument: {what}"),
}
}
/// Find and consume the first castable positional argument.
pub fn find<T>(&mut self) -> SourceResult<Option<T>>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
for (i, slot) in self.items.iter().enumerate() {
- if slot.name.is_none() && T::is(&slot.value) {
+ if slot.name.is_none() && T::castable(&slot.value.v) {
let value = self.items.remove(i).value;
let span = value.span;
- return T::cast(value).at(span).map(Some);
+ return T::from_value(value).at(span).map(Some);
}
}
Ok(None)
@@ -113,7 +117,7 @@ impl Args {
/// Find and consume all castable positional arguments.
pub fn all<T>(&mut self) -> SourceResult<Vec<T>>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
let mut list = vec![];
while let Some(value) = self.find()? {
@@ -126,7 +130,7 @@ impl Args {
/// error if the conversion fails.
pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
// We don't quit once we have a match because when multiple matches
// exist, we want to remove all of them and use the last one.
@@ -136,7 +140,7 @@ impl Args {
if self.items[i].name.as_deref() == Some(name) {
let value = self.items.remove(i).value;
let span = value.span;
- found = Some(T::cast(value).at(span)?);
+ found = Some(T::from_value(value).at(span)?);
} else {
i += 1;
}
@@ -147,7 +151,7 @@ impl Args {
/// Same as named, but with fallback to find.
pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>>
where
- T: Cast<Spanned<Value>>,
+ T: FromValue<Spanned<Value>>,
{
match self.named(name)? {
Some(value) => Ok(Some(value)),
@@ -167,13 +171,10 @@ impl Args {
/// argument.
pub fn finish(self) -> SourceResult<()> {
if let Some(arg) = self.items.first() {
- bail!(
- arg.span,
- match &arg.name {
- Some(name) => eco_format!("unexpected argument: {}", name),
- _ => eco_format!("unexpected argument"),
- }
- )
+ match &arg.name {
+ Some(name) => bail!(arg.span, "unexpected argument: {name}"),
+ _ => bail!(arg.span, "unexpected argument"),
+ }
}
Ok(())
}
diff --git a/src/eval/array.rs b/src/eval/array.rs
index d0b63b35..a7a1387b 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign};
use ecow::{eco_format, EcoString, EcoVec};
-use super::{ops, Args, Func, Value, Vm};
+use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm};
use crate::diag::{At, SourceResult, StrResult};
use crate::syntax::Span;
use crate::util::pretty_array_like;
@@ -14,11 +14,16 @@ use crate::util::pretty_array_like;
#[doc(hidden)]
macro_rules! __array {
($value:expr; $count:expr) => {
- $crate::eval::Array::from_vec($crate::eval::eco_vec![$value.into(); $count])
+ $crate::eval::Array::from($crate::eval::eco_vec![
+ $crate::eval::IntoValue::into_value($value);
+ $count
+ ])
};
($($value:expr),* $(,)?) => {
- $crate::eval::Array::from_vec($crate::eval::eco_vec![$($value.into()),*])
+ $crate::eval::Array::from($crate::eval::eco_vec![$(
+ $crate::eval::IntoValue::into_value($value)
+ ),*])
};
}
@@ -38,19 +43,14 @@ impl Array {
Self::default()
}
- /// Create a new array from an eco vector of values.
- pub fn from_vec(vec: EcoVec<Value>) -> Self {
- Self(vec)
- }
-
/// Return `true` if the length is 0.
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
/// The length of the array.
- pub fn len(&self) -> i64 {
- self.0.len() as i64
+ pub fn len(&self) -> usize {
+ self.0.len()
}
/// The first value in the array.
@@ -134,14 +134,14 @@ impl Array {
.filter(|&start| start <= self.0.len())
.ok_or_else(|| out_of_bounds(start, len))?;
- let end = end.unwrap_or(self.len());
+ let end = end.unwrap_or(self.len() as i64);
let end = self
.locate(end)
.filter(|&end| end <= self.0.len())
.ok_or_else(|| out_of_bounds(end, len))?
.max(start);
- Ok(Self::from_vec(self.0[start..end].into()))
+ Ok(self.0[start..end].into())
}
/// Whether the array contains a specific value.
@@ -182,7 +182,7 @@ impl Array {
kept.push(item.clone())
}
}
- Ok(Self::from_vec(kept))
+ Ok(kept.into())
}
/// Transform each item in the array with a function.
@@ -273,7 +273,7 @@ impl Array {
flat.push(item.clone());
}
}
- Self::from_vec(flat)
+ flat.into()
}
/// Returns a new array with reversed order.
@@ -317,9 +317,7 @@ impl Array {
pub fn zip(&self, other: Array) -> Array {
self.iter()
.zip(other)
- .map(|(first, second)| {
- Value::Array(Array::from_vec(eco_vec![first.clone(), second]))
- })
+ .map(|(first, second)| array![first.clone(), second].into_value())
.collect()
}
@@ -360,7 +358,7 @@ impl Array {
}
}
});
- result.map(|_| Self::from_vec(vec))
+ result.map(|_| vec.into())
}
/// Repeat this array `n` times.
@@ -385,19 +383,20 @@ impl Array {
/// Resolve an index.
fn locate(&self, index: i64) -> Option<usize> {
- usize::try_from(if index >= 0 { index } else { self.len().checked_add(index)? })
- .ok()
+ usize::try_from(if index >= 0 {
+ index
+ } else {
+ (self.len() as i64).checked_add(index)?
+ })
+ .ok()
}
/// Enumerate all items in the array.
pub fn enumerate(&self) -> Self {
- let v = self
- .iter()
+ self.iter()
.enumerate()
- .map(|(i, value)| array![i, value.clone()])
- .map(Value::Array)
- .collect();
- Self::from_vec(v)
+ .map(|(i, value)| array![i, value.clone()].into_value())
+ .collect()
}
}
@@ -453,6 +452,40 @@ impl<'a> IntoIterator for &'a Array {
}
}
+impl From<EcoVec<Value>> for Array {
+ fn from(v: EcoVec<Value>) -> Self {
+ Array(v)
+ }
+}
+
+impl From<&[Value]> for Array {
+ fn from(v: &[Value]) -> Self {
+ Array(v.into())
+ }
+}
+
+impl<T> Reflect for Vec<T> {
+ fn describe() -> CastInfo {
+ Array::describe()
+ }
+
+ fn castable(value: &Value) -> bool {
+ Array::castable(value)
+ }
+}
+
+impl<T: IntoValue> IntoValue for Vec<T> {
+ fn into_value(self) -> Value {
+ Value::Array(self.into_iter().map(IntoValue::into_value).collect())
+ }
+}
+
+impl<T: FromValue> FromValue for Vec<T> {
+ fn from_value(value: Value) -> StrResult<Self> {
+ value.cast::<Array>()?.into_iter().map(Value::cast).collect()
+ }
+}
+
/// The error message when the array is empty.
#[cold]
fn array_is_empty() -> EcoString {
@@ -461,17 +494,15 @@ fn array_is_empty() -> EcoString {
/// The out of bounds access error message.
#[cold]
-fn out_of_bounds(index: i64, len: i64) -> EcoString {
- eco_format!("array index out of bounds (index: {}, len: {})", index, len)
+fn out_of_bounds(index: i64, len: usize) -> EcoString {
+ eco_format!("array index out of bounds (index: {index}, len: {len})")
}
/// The out of bounds access error message when no default value was given.
#[cold]
-fn out_of_bounds_no_default(index: i64, len: i64) -> EcoString {
+fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString {
eco_format!(
- "array index out of bounds (index: {}, len: {}) \
+ "array index out of bounds (index: {index}, len: {len}) \
and no default value was specified",
- index,
- len
)
}
diff --git a/src/eval/auto.rs b/src/eval/auto.rs
new file mode 100644
index 00000000..e73b3f33
--- /dev/null
+++ b/src/eval/auto.rs
@@ -0,0 +1,39 @@
+use std::fmt::{self, Debug, Formatter};
+
+use super::{CastInfo, FromValue, IntoValue, Reflect, Value};
+use crate::diag::StrResult;
+
+/// A value that indicates a smart default.
+#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct AutoValue;
+
+impl IntoValue for AutoValue {
+ fn into_value(self) -> Value {
+ Value::Auto
+ }
+}
+
+impl FromValue for AutoValue {
+ fn from_value(value: Value) -> StrResult<Self> {
+ match value {
+ Value::Auto => Ok(Self),
+ _ => Err(Self::error(&value)),
+ }
+ }
+}
+
+impl Reflect for AutoValue {
+ fn describe() -> CastInfo {
+ CastInfo::Type("auto")
+ }
+
+ fn castable(value: &Value) -> bool {
+ matches!(value, Value::Auto)
+ }
+}
+
+impl Debug for AutoValue {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("auto")
+ }
+}
diff --git a/src/eval/cast.rs b/src/eval/cast.rs
index 7ef2f1d0..29cf5f71 100644
--- a/src/eval/cast.rs
+++ b/src/eval/cast.rs
@@ -1,278 +1,185 @@
-pub use typst_macros::{cast_from_value, cast_to_value, Cast};
+pub use typst_macros::{cast, Cast};
-use std::num::{NonZeroI64, NonZeroU64, NonZeroUsize};
+use std::fmt::Write;
use std::ops::Add;
use ecow::EcoString;
-use super::{Array, Str, Value};
-use crate::diag::StrResult;
-use crate::eval::Type;
-use crate::geom::Length;
-use crate::syntax::Spanned;
+use super::Value;
+use crate::diag::{At, SourceResult, StrResult};
+use crate::syntax::{Span, Spanned};
use crate::util::separated_list;
-/// Cast from a value to a specific type.
-pub trait Cast<V = Value>: Sized {
- /// Check whether the value is castable to `Self`.
- fn is(value: &V) -> bool;
-
- /// Try to cast the value into an instance of `Self`.
- fn cast(value: V) -> StrResult<Self>;
-
- /// Describe the acceptable values.
+/// Determine details of a type.
+///
+/// Type casting works as follows:
+/// - [`Reflect for T`](Reflect) describes the possible Typst values for `T`
+/// (for documentation and autocomplete).
+/// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value`
+/// (infallible)
+/// - [`FromValue for T`](FromValue) is for conversion from `Value -> T`
+/// (fallible).
+///
+/// We can't use `TryFrom<Value>` due to conflicting impls. We could use
+/// `From<T> for Value`, but that inverses the impl and leads to tons of
+/// `.into()` all over the place that become hard to decipher.
+pub trait Reflect {
+ /// Describe the acceptable values for this type.
fn describe() -> CastInfo;
- /// Produce an error for an inacceptable value.
- fn error(value: Value) -> StrResult<Self> {
- Err(Self::describe().error(&value))
+ /// Whether the given value can be converted to `T`.
+ ///
+ /// This exists for performance. The check could also be done through the
+ /// [`CastInfo`], but it would be much more expensive (heap allocation +
+ /// dynamic checks instead of optimized machine code for each type).
+ fn castable(value: &Value) -> bool;
+
+ /// Produce an error message for an inacceptable value.
+ ///
+ /// ```
+ /// # use typst::eval::{Int, Reflect, Value};
+ /// assert_eq!(
+ /// <Int as Reflect>::error(Value::None),
+ /// "expected integer, found none",
+ /// );
+ /// ```
+ fn error(found: &Value) -> EcoString {
+ Self::describe().error(found)
}
}
-impl Cast for Value {
- fn is(_: &Value) -> bool {
- true
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- Ok(value)
- }
-
+impl Reflect for Value {
fn describe() -> CastInfo {
CastInfo::Any
}
-}
-impl<T: Cast> Cast<Spanned<Value>> for T {
- fn is(value: &Spanned<Value>) -> bool {
- T::is(&value.v)
- }
-
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- T::cast(value.v)
+ fn castable(_: &Value) -> bool {
+ true
}
+}
+impl<T: Reflect> Reflect for Spanned<T> {
fn describe() -> CastInfo {
T::describe()
}
-}
-
-impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
- fn is(value: &Spanned<Value>) -> bool {
- T::is(&value.v)
- }
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- let span = value.span;
- T::cast(value.v).map(|t| Spanned::new(t, span))
+ fn castable(value: &Value) -> bool {
+ T::castable(value)
}
+}
+impl<T: Reflect> Reflect for StrResult<T> {
fn describe() -> CastInfo {
T::describe()
}
-}
-
-cast_to_value! {
- v: u8 => Value::Int(v as i64)
-}
-
-cast_to_value! {
- v: u16 => Value::Int(v as i64)
-}
-
-cast_from_value! {
- u32,
- int: i64 => int.try_into().map_err(|_| {
- if int < 0 {
- "number must be at least zero"
- } else {
- "number too large"
- }
- })?,
-}
-cast_to_value! {
- v: u32 => Value::Int(v as i64)
-}
-
-cast_from_value! {
- u64,
- int: i64 => int.try_into().map_err(|_| {
- if int < 0 {
- "number must be at least zero"
- } else {
- "number too large"
- }
- })?,
-}
-
-cast_to_value! {
- v: u64 => Value::Int(v as i64)
-}
-
-cast_from_value! {
- usize,
- int: i64 => int.try_into().map_err(|_| {
- if int < 0 {
- "number must be at least zero"
- } else {
- "number too large"
- }
- })?,
-}
-
-cast_to_value! {
- v: usize => Value::Int(v as i64)
-}
-
-cast_to_value! {
- v: i32 => Value::Int(v as i64)
-}
-
-cast_from_value! {
- NonZeroI64,
- int: i64 => int.try_into()
- .map_err(|_| if int == 0 {
- "number must not be zero"
- } else {
- "number too large"
- })?,
-}
-
-cast_to_value! {
- v: NonZeroI64 => Value::Int(v.get())
-}
-
-cast_from_value! {
- NonZeroU64,
- int: i64 => int
- .try_into()
- .and_then(|int: u64| int.try_into())
- .map_err(|_| if int <= 0 {
- "number must be positive"
- } else {
- "number too large"
- })?,
+ fn castable(value: &Value) -> bool {
+ T::castable(value)
+ }
}
-cast_to_value! {
- v: NonZeroU64 => Value::Int(v.get() as i64)
-}
+impl<T: Reflect> Reflect for SourceResult<T> {
+ fn describe() -> CastInfo {
+ T::describe()
+ }
-cast_from_value! {
- NonZeroUsize,
- int: i64 => int
- .try_into()
- .and_then(|int: usize| int.try_into())
- .map_err(|_| if int <= 0 {
- "number must be positive"
- } else {
- "number too large"
- })?,
+ fn castable(value: &Value) -> bool {
+ T::castable(value)
+ }
}
-cast_to_value! {
- v: NonZeroUsize => Value::Int(v.get() as i64)
-}
+impl<T: Reflect> Reflect for &T {
+ fn describe() -> CastInfo {
+ T::describe()
+ }
-cast_from_value! {
- char,
- string: Str => {
- let mut chars = string.chars();
- match (chars.next(), chars.next()) {
- (Some(c), None) => c,
- _ => Err("expected exactly one character")?,
- }
- },
+ fn castable(value: &Value) -> bool {
+ T::castable(value)
+ }
}
-cast_to_value! {
- v: char => Value::Str(v.into())
-}
+impl<T: Reflect> Reflect for &mut T {
+ fn describe() -> CastInfo {
+ T::describe()
+ }
-cast_to_value! {
- v: &str => Value::Str(v.into())
+ fn castable(value: &Value) -> bool {
+ T::castable(value)
+ }
}
-cast_from_value! {
- EcoString,
- v: Str => v.into(),
+/// Cast a Rust type into a Typst [`Value`].
+///
+/// See also: [`Reflect`].
+pub trait IntoValue {
+ /// Cast this type into a value.
+ fn into_value(self) -> Value;
}
-cast_to_value! {
- v: EcoString => Value::Str(v.into())
+impl IntoValue for Value {
+ fn into_value(self) -> Value {
+ self
+ }
}
-cast_from_value! {
- String,
- v: Str => v.into(),
+impl<T: IntoValue> IntoValue for Spanned<T> {
+ fn into_value(self) -> Value {
+ self.v.into_value()
+ }
}
-cast_to_value! {
- v: String => Value::Str(v.into())
+/// Cast a Rust type or result into a [`SourceResult<Value>`].
+///
+/// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into
+/// [`SourceResult<Value>`] by `Ok`-wrapping or adding span information.
+pub trait IntoResult {
+ /// Cast this type into a value.
+ fn into_result(self, span: Span) -> SourceResult<Value>;
}
-impl<T: Cast> Cast for Option<T> {
- fn is(value: &Value) -> bool {
- matches!(value, Value::None) || T::is(value)
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::None => Ok(None),
- v if T::is(&v) => Ok(Some(T::cast(v)?)),
- _ => <Self as Cast>::error(value),
- }
- }
-
- fn describe() -> CastInfo {
- T::describe() + CastInfo::Type("none")
+impl<T: IntoValue> IntoResult for T {
+ fn into_result(self, _: Span) -> SourceResult<Value> {
+ Ok(self.into_value())
}
}
-impl<T: Into<Value>> From<Spanned<T>> for Value {
- fn from(spanned: Spanned<T>) -> Self {
- spanned.v.into()
+impl<T: IntoValue> IntoResult for StrResult<T> {
+ fn into_result(self, span: Span) -> SourceResult<Value> {
+ self.map(IntoValue::into_value).at(span)
}
}
-impl<T: Into<Value>> From<Option<T>> for Value {
- fn from(v: Option<T>) -> Self {
- match v {
- Some(v) => v.into(),
- None => Value::None,
- }
+impl<T: IntoValue> IntoResult for SourceResult<T> {
+ fn into_result(self, _: Span) -> SourceResult<Value> {
+ self.map(IntoValue::into_value)
}
}
-impl<T: Cast> Cast for Vec<T> {
- fn is(value: &Value) -> bool {
- Array::is(value)
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- value.cast::<Array>()?.into_iter().map(Value::cast).collect()
- }
-
- fn describe() -> CastInfo {
- <Array as Cast>::describe()
- }
+/// Try to cast a Typst [`Value`] into a Rust type.
+///
+/// See also: [`Reflect`].
+pub trait FromValue<V = Value>: Sized + Reflect {
+ /// Try to cast the value into an instance of `Self`.
+ fn from_value(value: V) -> StrResult<Self>;
}
-impl<T: Into<Value>> From<Vec<T>> for Value {
- fn from(v: Vec<T>) -> Self {
- Value::Array(v.into_iter().map(Into::into).collect())
+impl FromValue for Value {
+ fn from_value(value: Value) -> StrResult<Self> {
+ Ok(value)
}
}
-/// A container for a variadic argument.
-pub trait Variadics {
- /// The contained type.
- type Inner;
+impl<T: FromValue> FromValue<Spanned<Value>> for T {
+ fn from_value(value: Spanned<Value>) -> StrResult<Self> {
+ T::from_value(value.v)
+ }
}
-impl<T> Variadics for Vec<T> {
- type Inner = T;
+impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
+ fn from_value(value: Spanned<Value>) -> StrResult<Self> {
+ let span = value.span;
+ T::from_value(value.v).map(|t| Spanned::new(t, span))
+ }
}
/// Describes a possible value for a cast.
@@ -332,10 +239,10 @@ impl CastInfo {
}
if_chain::if_chain! {
if let Value::Int(i) = found;
- if parts.iter().any(|p| p == Length::TYPE_NAME);
+ if parts.iter().any(|p| p == "length");
if !matching_type;
then {
- msg.push_str(&format!(": a length needs a unit – did you mean {i}pt?"));
+ write!(msg, ": a length needs a unit – did you mean {i}pt?").unwrap();
}
};
@@ -373,19 +280,37 @@ impl Add for CastInfo {
}
}
-/// Castable from nothing.
+/// A container for a variadic argument.
+pub trait Variadics {
+ /// The contained type.
+ type Inner;
+}
+
+impl<T> Variadics for Vec<T> {
+ type Inner = T;
+}
+
+/// An uninhabitable type.
pub enum Never {}
-impl Cast for Never {
- fn is(_: &Value) -> bool {
+impl Reflect for Never {
+ fn describe() -> CastInfo {
+ CastInfo::Union(vec![])
+ }
+
+ fn castable(_: &Value) -> bool {
false
}
+}
- fn cast(value: Value) -> StrResult<Self> {
- <Self as Cast>::error(value)
+impl IntoValue for Never {
+ fn into_value(self) -> Value {
+ match self {}
}
+}
- fn describe() -> CastInfo {
- CastInfo::Union(vec![])
+impl FromValue for Never {
+ fn from_value(value: Value) -> StrResult<Self> {
+ Err(Self::error(&value))
}
}
diff --git a/src/eval/datetime.rs b/src/eval/datetime.rs
index 47574bae..57d0683d 100644
--- a/src/eval/datetime.rs
+++ b/src/eval/datetime.rs
@@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString, EcoVec};
use time::error::{Format, InvalidFormatDescription};
use time::{format_description, PrimitiveDateTime};
-use crate::eval::cast_from_value;
+use crate::eval::cast;
use crate::util::pretty_array_like;
/// A datetime object that represents either a date, a time or a combination of
@@ -153,8 +153,8 @@ impl Debug for Datetime {
}
}
-cast_from_value! {
- Datetime: "datetime",
+cast! {
+ type Datetime: "datetime",
}
/// Format the `Format` error of the time crate in an appropriate way.
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index 49a60147..3e6233ae 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -17,8 +17,8 @@ macro_rules! __dict {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = $crate::eval::IndexMap::new();
- $(map.insert($key.into(), $value.into());)*
- $crate::eval::Dict::from_map(map)
+ $(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)*
+ $crate::eval::Dict::from(map)
}};
}
@@ -38,19 +38,14 @@ impl Dict {
Self::default()
}
- /// Create a new dictionary from a mapping of strings to values.
- pub fn from_map(map: IndexMap<Str, Value>) -> Self {
- Self(Arc::new(map))
- }
-
/// Whether the dictionary is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// The number of pairs in the dictionary.
- pub fn len(&self) -> i64 {
- self.0.len() as i64
+ pub fn len(&self) -> usize {
+ self.0.len()
}
/// Borrow the value the given `key` maps to,
@@ -217,6 +212,12 @@ impl<'a> IntoIterator for &'a Dict {
}
}
+impl From<IndexMap<Str, Value>> for Dict {
+ fn from(map: IndexMap<Str, Value>) -> Self {
+ Self(Arc::new(map))
+ }
+}
+
/// The missing key access error message.
#[cold]
fn missing_key(key: &str) -> EcoString {
diff --git a/src/eval/func.rs b/src/eval/func.rs
index a224c5b8..86070fc7 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -1,5 +1,3 @@
-pub use typst_macros::func;
-
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
@@ -9,7 +7,7 @@ use ecow::eco_format;
use once_cell::sync::Lazy;
use super::{
- cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm,
+ cast, Args, CastInfo, Eval, Flow, IntoValue, Route, Scope, Scopes, Tracer, Value, Vm,
};
use crate::diag::{bail, SourceResult, StrResult};
use crate::model::{ElemFunc, Introspector, Locator, Vt};
@@ -119,10 +117,10 @@ impl Func {
/// Call the function with a Vt.
#[tracing::instrument(skip_all)]
- pub fn call_vt(
+ pub fn call_vt<T: IntoValue>(
&self,
vt: &mut Vt,
- args: impl IntoIterator<Item = Value>,
+ args: impl IntoIterator<Item = T>,
) -> SourceResult<Value> {
let route = Route::default();
let id = SourceId::detached();
@@ -233,13 +231,9 @@ impl From<&'static NativeFunc> for Func {
}
}
-impl<F> From<F> for Value
-where
- F: Fn() -> &'static NativeFunc,
-{
- fn from(f: F) -> Self {
- Value::Func(f().into())
- }
+cast! {
+ &'static NativeFunc,
+ self => Value::Func(self.into()),
}
/// Details about a function.
@@ -249,16 +243,16 @@ pub struct FuncInfo {
pub name: &'static str,
/// The display name of the function.
pub display: &'static str,
- /// A string of keywords.
+ /// A string of search keywords.
pub keywords: Option<&'static str>,
+ /// Which category the function is part of.
+ pub category: &'static str,
/// Documentation for the function.
pub docs: &'static str,
/// Details about the function's parameters.
pub params: Vec<ParamInfo>,
- /// Valid types for the return value.
- pub returns: Vec<&'static str>,
- /// Which category the function is part of.
- pub category: &'static str,
+ /// Valid values for the return value.
+ pub returns: CastInfo,
/// The function's own scope of fields and sub-functions.
pub scope: Scope,
}
@@ -311,6 +305,7 @@ pub(super) struct Closure {
pub body: Expr,
}
+/// A closure parameter.
#[derive(Hash)]
pub enum Param {
/// A positional parameter: `x`.
@@ -362,7 +357,7 @@ impl Closure {
// Parse the arguments according to the parameter list.
let num_pos_params =
closure.params.iter().filter(|p| matches!(p, Param::Pos(_))).count();
- let num_pos_args = args.to_pos().len() as usize;
+ let num_pos_args = args.to_pos().len();
let sink_size = num_pos_args.checked_sub(num_pos_params);
let mut sink = None;
@@ -425,8 +420,9 @@ impl From<Closure> for Func {
}
}
-cast_to_value! {
- v: Closure => Value::Func(v.into())
+cast! {
+ Closure,
+ self => Value::Func(self.into()),
}
/// A visitor that determines which variables to capture for a closure.
diff --git a/src/eval/int.rs b/src/eval/int.rs
new file mode 100644
index 00000000..4e081617
--- /dev/null
+++ b/src/eval/int.rs
@@ -0,0 +1,81 @@
+use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize};
+
+use super::{cast, Value};
+
+macro_rules! signed_int {
+ ($($ty:ty)*) => {
+ $(cast! {
+ $ty,
+ self => Value::Int(self as i64),
+ v: i64 => v.try_into().map_err(|_| "number too large")?,
+ })*
+ }
+}
+
+macro_rules! unsigned_int {
+ ($($ty:ty)*) => {
+ $(cast! {
+ $ty,
+ self => Value::Int(self as i64),
+ v: i64 => v.try_into().map_err(|_| {
+ if v < 0 {
+ "number must be at least zero"
+ } else {
+ "number too large"
+ }
+ })?,
+ })*
+ }
+}
+
+macro_rules! signed_nonzero {
+ ($($ty:ty)*) => {
+ $(cast! {
+ $ty,
+ self => Value::Int(self.get() as i64),
+ v: i64 => v
+ .try_into()
+ .ok()
+ .and_then($ty::new)
+ .ok_or_else(|| if v == 0 {
+ "number must not be zero"
+ } else {
+ "number too large"
+ })?,
+ })*
+ }
+}
+
+macro_rules! unsigned_nonzero {
+ ($($ty:ty)*) => {
+ $(cast! {
+ $ty,
+ self => Value::Int(self.get() as i64),
+ v: i64 => v
+ .try_into()
+ .ok()
+ .and_then($ty::new)
+ .ok_or_else(|| if v <= 0 {
+ "number must be positive"
+ } else {
+ "number too large"
+ })?,
+ })*
+ }
+}
+
+signed_int! {
+ i8 i16 i32 isize
+}
+
+unsigned_int! {
+ u8 u16 u32 u64 usize
+}
+
+signed_nonzero! {
+ NonZeroI64 NonZeroIsize
+}
+
+unsigned_nonzero! {
+ NonZeroU64 NonZeroUsize
+}
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index f57bf84d..62ac4095 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -2,7 +2,7 @@
use ecow::EcoString;
-use super::{Args, Str, Value, Vm};
+use super::{Args, IntoValue, Str, Value, Vm};
use crate::diag::{At, SourceResult};
use crate::eval::Datetime;
use crate::model::{Location, Selector};
@@ -21,20 +21,20 @@ pub fn call(
let output = match value {
Value::Color(color) => match method {
- "lighten" => Value::Color(color.lighten(args.expect("amount")?)),
- "darken" => Value::Color(color.darken(args.expect("amount")?)),
- "negate" => Value::Color(color.negate()),
+ "lighten" => color.lighten(args.expect("amount")?).into_value(),
+ "darken" => color.darken(args.expect("amount")?).into_value(),
+ "negate" => color.negate().into_value(),
_ => return missing(),
},
Value::Str(string) => match method {
- "len" => Value::Int(string.len()),
- "first" => Value::Str(string.first().at(span)?),
- "last" => Value::Str(string.last().at(span)?),
+ "len" => string.len().into_value(),
+ "first" => string.first().at(span)?.into_value(),
+ "last" => string.last().at(span)?.into_value(),
"at" => {
let index = args.expect("index")?;
let default = args.named::<EcoString>("default")?;
- Value::Str(string.at(index, default.as_deref()).at(span)?)
+ string.at(index, default.as_deref()).at(span)?.into_value()
}
"slice" => {
let start = args.expect("start")?;
@@ -42,56 +42,50 @@ pub fn call(
if end.is_none() {
end = args.named("count")?.map(|c: i64| start + c);
}
- Value::Str(string.slice(start, end).at(span)?)
+ string.slice(start, end).at(span)?.into_value()
}
- "clusters" => Value::Array(string.clusters()),
- "codepoints" => Value::Array(string.codepoints()),
- "contains" => Value::Bool(string.contains(args.expect("pattern")?)),
- "starts-with" => Value::Bool(string.starts_with(args.expect("pattern")?)),
- "ends-with" => Value::Bool(string.ends_with(args.expect("pattern")?)),
- "find" => {
- string.find(args.expect("pattern")?).map_or(Value::None, Value::Str)
- }
- "position" => string
- .position(args.expect("pattern")?)
- .map_or(Value::None, Value::Int),
- "match" => string
- .match_(args.expect("pattern")?)
- .map_or(Value::None, Value::Dict),
- "matches" => Value::Array(string.matches(args.expect("pattern")?)),
+ "clusters" => string.clusters().into_value(),
+ "codepoints" => string.codepoints().into_value(),
+ "contains" => string.contains(args.expect("pattern")?).into_value(),
+ "starts-with" => string.starts_with(args.expect("pattern")?).into_value(),
+ "ends-with" => string.ends_with(args.expect("pattern")?).into_value(),
+ "find" => string.find(args.expect("pattern")?).into_value(),
+ "position" => string.position(args.expect("pattern")?).into_value(),
+ "match" => string.match_(args.expect("pattern")?).into_value(),
+ "matches" => string.matches(args.expect("pattern")?).into_value(),
"replace" => {
let pattern = args.expect("pattern")?;
let with = args.expect("string or function")?;
let count = args.named("count")?;
- Value::Str(string.replace(vm, pattern, with, count)?)
+ string.replace(vm, pattern, with, count)?.into_value()
}
"trim" => {
let pattern = args.eat()?;
let at = args.named("at")?;
let repeat = args.named("repeat")?.unwrap_or(true);
- Value::Str(string.trim(pattern, at, repeat))
+ string.trim(pattern, at, repeat).into_value()
}
- "split" => Value::Array(string.split(args.eat()?)),
+ "split" => string.split(args.eat()?).into_value(),
_ => return missing(),
},
Value::Content(content) => match method {
- "func" => content.func().into(),
- "has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
+ "func" => content.func().into_value(),
+ "has" => content.has(&args.expect::<EcoString>("field")?).into_value(),
"at" => content
.at(&args.expect::<EcoString>("field")?, args.named("default")?)
.at(span)?,
- "fields" => Value::Dict(content.dict()),
+ "fields" => content.dict().into_value(),
"location" => content
.location()
.ok_or("this method can only be called on content returned by query(..)")
.at(span)?
- .into(),
+ .into_value(),
_ => return missing(),
},
Value::Array(array) => match method {
- "len" => Value::Int(array.len()),
+ "len" => array.len().into_value(),
"first" => array.first().at(span)?.clone(),
"last" => array.last().at(span)?.clone(),
"at" => array
@@ -104,117 +98,104 @@ pub fn call(
if end.is_none() {
end = args.named("count")?.map(|c: i64| start + c);
}
- Value::Array(array.slice(start, end).at(span)?)
+ array.slice(start, end).at(span)?.into_value()
}
- "contains" => Value::Bool(array.contains(&args.expect("value")?)),
- "find" => array.find(vm, args.expect("function")?)?.unwrap_or(Value::None),
- "position" => array
- .position(vm, args.expect("function")?)?
- .map_or(Value::None, Value::Int),
- "filter" => Value::Array(array.filter(vm, args.expect("function")?)?),
- "map" => Value::Array(array.map(vm, args.expect("function")?)?),
+ "contains" => array.contains(&args.expect("value")?).into_value(),
+ "find" => array.find(vm, args.expect("function")?)?.into_value(),
+ "position" => array.position(vm, args.expect("function")?)?.into_value(),
+ "filter" => array.filter(vm, args.expect("function")?)?.into_value(),
+ "map" => array.map(vm, args.expect("function")?)?.into_value(),
"fold" => {
array.fold(vm, args.expect("initial value")?, args.expect("function")?)?
}
"sum" => array.sum(args.named("default")?, span)?,
"product" => array.product(args.named("default")?, span)?,
- "any" => Value::Bool(array.any(vm, args.expect("function")?)?),
- "all" => Value::Bool(array.all(vm, args.expect("function")?)?),
- "flatten" => Value::Array(array.flatten()),
- "rev" => Value::Array(array.rev()),
- "split" => Value::Array(array.split(args.expect("separator")?)),
+ "any" => array.any(vm, args.expect("function")?)?.into_value(),
+ "all" => array.all(vm, args.expect("function")?)?.into_value(),
+ "flatten" => array.flatten().into_value(),
+ "rev" => array.rev().into_value(),
+ "split" => array.split(args.expect("separator")?).into_value(),
"join" => {
let sep = args.eat()?;
let last = args.named("last")?;
array.join(sep, last).at(span)?
}
- "sorted" => Value::Array(array.sorted(vm, span, args.named("key")?)?),
- "zip" => Value::Array(array.zip(args.expect("other")?)),
- "enumerate" => Value::Array(array.enumerate()),
+ "sorted" => array.sorted(vm, span, args.named("key")?)?.into_value(),
+ "zip" => array.zip(args.expect("other")?).into_value(),
+ "enumerate" => array.enumerate().into_value(),
_ => return missing(),
},
Value::Dict(dict) => match method {
- "len" => Value::Int(dict.len()),
+ "len" => dict.len().into_value(),
"at" => dict
.at(&args.expect::<Str>("key")?, args.named("default")?.as_ref())
.at(span)?
.clone(),
- "keys" => Value::Array(dict.keys()),
- "values" => Value::Array(dict.values()),
- "pairs" => Value::Array(dict.pairs()),
+ "keys" => dict.keys().into_value(),
+ "values" => dict.values().into_value(),
+ "pairs" => dict.pairs().into_value(),
_ => return missing(),
},
Value::Func(func) => match method {
- "with" => Value::Func(func.with(args.take())),
+ "with" => func.with(args.take()).into_value(),
"where" => {
let fields = args.to_named();
args.items.retain(|arg| arg.name.is_none());
- Value::dynamic(
- func.element()
- .ok_or("`where()` can only be called on element functions")
- .at(span)?
- .where_(fields),
- )
+ func.element()
+ .ok_or("`where()` can only be called on element functions")
+ .at(span)?
+ .where_(fields)
+ .into_value()
}
_ => return missing(),
},
Value::Args(args) => match method {
- "pos" => Value::Array(args.to_pos()),
- "named" => Value::Dict(args.to_named()),
+ "pos" => args.to_pos().into_value(),
+ "named" => args.to_named().into_value(),
_ => return missing(),
},
Value::Dyn(dynamic) => {
if let Some(location) = dynamic.downcast::<Location>() {
match method {
- "page" => vm.vt.introspector.page(*location).into(),
- "position" => vm.vt.introspector.position(*location).into(),
+ "page" => vm.vt.introspector.page(*location).into_value(),
+ "position" => vm.vt.introspector.position(*location).into_value(),
"page-numbering" => vm.vt.introspector.page_numbering(*location),
_ => return missing(),
}
} else if let Some(selector) = dynamic.downcast::<Selector>() {
match method {
- "or" => selector.clone().or(args.all::<Selector>()?).into(),
- "and" => selector.clone().and(args.all::<Selector>()?).into(),
+ "or" => selector.clone().or(args.all::<Selector>()?).into_value(),
+ "and" => selector.clone().and(args.all::<Selector>()?).into_value(),
"before" => {
let location = args.expect::<Selector>("selector")?;
let inclusive =
args.named_or_find::<bool>("inclusive")?.unwrap_or(true);
- selector.clone().before(location, inclusive).into()
+ selector.clone().before(location, inclusive).into_value()
}
"after" => {
let location = args.expect::<Selector>("selector")?;
let inclusive =
args.named_or_find::<bool>("inclusive")?.unwrap_or(true);
- selector.clone().after(location, inclusive).into()
+ selector.clone().after(location, inclusive).into_value()
}
_ => return missing(),
}
} else if let Some(&datetime) = dynamic.downcast::<Datetime>() {
match method {
- "display" => datetime.display(args.eat()?).at(args.span)?.into(),
- "year" => {
- datetime.year().map_or(Value::None, |y| Value::Int(y.into()))
- }
- "month" => {
- datetime.month().map_or(Value::None, |m| Value::Int(m.into()))
- }
- "weekday" => {
- datetime.weekday().map_or(Value::None, |w| Value::Int(w.into()))
- }
- "day" => datetime.day().map_or(Value::None, |d| Value::Int(d.into())),
- "hour" => {
- datetime.hour().map_or(Value::None, |h| Value::Int(h.into()))
- }
- "minute" => {
- datetime.minute().map_or(Value::None, |m| Value::Int(m.into()))
- }
- "second" => {
- datetime.second().map_or(Value::None, |s| Value::Int(s.into()))
+ "display" => {
+ datetime.display(args.eat()?).at(args.span)?.into_value()
}
+ "year" => datetime.year().into_value(),
+ "month" => datetime.month().into_value(),
+ "weekday" => datetime.weekday().into_value(),
+ "day" => datetime.day().into_value(),
+ "hour" => datetime.hour().into_value(),
+ "minute" => datetime.minute().into_value(),
+ "second" => datetime.second().into_value(),
_ => return missing(),
}
} else {
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 2499ae22..ab75bfb4 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -13,31 +13,45 @@ mod str;
#[macro_use]
mod value;
mod args;
+mod auto;
mod datetime;
mod func;
+mod int;
mod methods;
mod module;
+mod none;
pub mod ops;
mod scope;
mod symbol;
#[doc(hidden)]
-pub use once_cell::sync::Lazy;
-
-pub use self::args::*;
-pub use self::array::*;
-pub use self::cast::*;
-pub use self::datetime::*;
-pub use self::dict::*;
-pub use self::func::*;
-pub use self::library::*;
-pub use self::module::*;
-pub use self::scope::*;
-pub use self::str::*;
-pub use self::symbol::*;
-pub use self::value::*;
-
-pub(crate) use self::methods::methods_on;
+pub use {
+ self::library::LANG_ITEMS,
+ ecow::{eco_format, eco_vec},
+ indexmap::IndexMap,
+ once_cell::sync::Lazy,
+};
+
+#[doc(inline)]
+pub use typst_macros::{func, symbols};
+
+pub use self::args::{Arg, Args};
+pub use self::array::{array, Array};
+pub use self::auto::AutoValue;
+pub use self::cast::{
+ cast, Cast, CastInfo, FromValue, IntoResult, IntoValue, Never, Reflect, Variadics,
+};
+pub use self::datetime::Datetime;
+pub use self::dict::{dict, Dict};
+pub use self::func::{Func, FuncInfo, NativeFunc, Param, ParamInfo};
+pub use self::library::{set_lang_items, LangItems, Library};
+pub use self::methods::methods_on;
+pub use self::module::Module;
+pub use self::none::NoneValue;
+pub use self::scope::{Scope, Scopes};
+pub use self::str::{format_str, Regex, Str};
+pub use self::symbol::Symbol;
+pub use self::value::{Dynamic, Type, Value};
use std::collections::HashSet;
use std::mem;
@@ -47,6 +61,7 @@ use comemo::{Track, Tracked, TrackedMut, Validate};
use ecow::{EcoString, EcoVec};
use unicode_segmentation::UnicodeSegmentation;
+use self::func::{CapturesVisitor, Closure};
use crate::diag::{
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
};
@@ -214,8 +229,8 @@ impl<'a> Vm<'a> {
/// Define a variable in the current scope.
#[tracing::instrument(skip_all)]
- pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) {
- let value = value.into();
+ pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
+ let value = value.into_value();
if self.traced == Some(var.span()) {
self.vt.tracer.trace(value.clone());
}
@@ -934,7 +949,7 @@ impl Eval for ast::Array {
}
}
- Ok(Array::from_vec(vec))
+ Ok(vec.into())
}
}
@@ -965,7 +980,7 @@ impl Eval for ast::Dict {
}
}
- Ok(Dict::from_map(map))
+ Ok(map.into())
}
}
@@ -1285,23 +1300,23 @@ impl ast::Pattern {
for p in destruct.bindings() {
match p {
ast::DestructuringKind::Normal(expr) => {
- let Ok(v) = value.at(i, None) else {
+ let Ok(v) = value.at(i as i64, None) else {
bail!(expr.span(), "not enough elements to destructure");
};
f(vm, expr, v.clone())?;
i += 1;
}
ast::DestructuringKind::Sink(spread) => {
- let sink_size = (1 + value.len() as usize)
- .checked_sub(destruct.bindings().count());
- let sink =
- sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok());
+ let sink_size =
+ (1 + value.len()).checked_sub(destruct.bindings().count());
+ let sink = sink_size
+ .and_then(|s| value.slice(i as i64, Some((i + s) as i64)).ok());
if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
if let Some(expr) = spread.expr() {
f(vm, expr, Value::Array(sink.clone()))?;
}
- i += sink_size as i64;
+ i += sink_size;
} else {
bail!(self.span(), "not enough elements to destructure")
}
@@ -1590,7 +1605,7 @@ impl Eval for ast::ForLoop {
#[allow(unused_parens)]
for value in $iter {
- $pat.define(vm, Value::from(value))?;
+ $pat.define(vm, value.into_value())?;
let body = self.body();
let value = body.eval(vm)?;
@@ -1644,7 +1659,7 @@ impl Eval for ast::ForLoop {
}
/// Applies imports from `import` to the current scope.
-fn apply_imports<V: Into<Value>>(
+fn apply_imports<V: IntoValue>(
imports: Option<ast::Imports>,
vm: &mut Vm,
source_value: V,
diff --git a/src/eval/none.rs b/src/eval/none.rs
new file mode 100644
index 00000000..ab7644a7
--- /dev/null
+++ b/src/eval/none.rs
@@ -0,0 +1,74 @@
+use std::fmt::{self, Debug, Formatter};
+
+use super::{cast, CastInfo, FromValue, IntoValue, Reflect, Value};
+use crate::diag::StrResult;
+
+/// A value that indicates the absence of any other value.
+#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct NoneValue;
+
+impl Reflect for NoneValue {
+ fn describe() -> CastInfo {
+ CastInfo::Type("none")
+ }
+
+ fn castable(value: &Value) -> bool {
+ matches!(value, Value::None)
+ }
+}
+
+impl IntoValue for NoneValue {
+ fn into_value(self) -> Value {
+ Value::None
+ }
+}
+
+impl FromValue for NoneValue {
+ fn from_value(value: Value) -> StrResult<Self> {
+ match value {
+ Value::None => Ok(Self),
+ _ => Err(Self::error(&value)),
+ }
+ }
+}
+
+impl Debug for NoneValue {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("none")
+ }
+}
+
+cast! {
+ (),
+ self => Value::None,
+ _: NoneValue => (),
+}
+
+impl<T: Reflect> Reflect for Option<T> {
+ fn describe() -> CastInfo {
+ T::describe() + NoneValue::describe()
+ }
+
+ fn castable(value: &Value) -> bool {
+ NoneValue::castable(value) || T::castable(value)
+ }
+}
+
+impl<T: IntoValue> IntoValue for Option<T> {
+ fn into_value(self) -> Value {
+ match self {
+ Some(v) => v.into_value(),
+ None => Value::None,
+ }
+ }
+}
+
+impl<T: FromValue> FromValue for Option<T> {
+ fn from_value(value: Value) -> StrResult<Self> {
+ match value {
+ Value::None => Ok(None),
+ v if T::castable(&v) => Ok(Some(T::from_value(v)?)),
+ _ => Err(Self::error(&value)),
+ }
+ }
+}
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index 3e397c30..bea2ea82 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -209,8 +209,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
(Int(a), Str(b)) => Str(b.repeat(a)?),
(Array(a), Int(b)) => Array(a.repeat(b)?),
(Int(a), Array(b)) => Array(b.repeat(a)?),
- (Content(a), Int(b)) => Content(a.repeat(b)?),
- (Int(a), Content(b)) => Content(b.repeat(a)?),
+ (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)),
+ (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)),
(a, b) => mismatch!("cannot multiply {} with {}", a, b),
})
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index 83d1703e..bc62feb1 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -4,7 +4,7 @@ use std::hash::Hash;
use ecow::{eco_format, EcoString};
-use super::{Library, Value};
+use super::{IntoValue, Library, Value};
use crate::diag::StrResult;
/// A stack of scopes.
@@ -95,7 +95,7 @@ impl Scope {
/// Bind a value to a name.
#[track_caller]
- pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
+ pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) {
let name = name.into();
#[cfg(debug_assertions)]
@@ -103,16 +103,13 @@ impl Scope {
panic!("duplicate definition: {name}");
}
- self.0.insert(name, Slot::new(value.into(), Kind::Normal));
+ self.0.insert(name, Slot::new(value.into_value(), Kind::Normal));
}
/// Define a captured, immutable binding.
- pub fn define_captured(
- &mut self,
- var: impl Into<EcoString>,
- value: impl Into<Value>,
- ) {
- self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured));
+ pub fn define_captured(&mut self, var: impl Into<EcoString>, value: impl IntoValue) {
+ self.0
+ .insert(var.into(), Slot::new(value.into_value(), Kind::Captured));
}
/// Try to access a variable immutably.
diff --git a/src/eval/str.rs b/src/eval/str.rs
index 567ee5bd..48ba8311 100644
--- a/src/eval/str.rs
+++ b/src/eval/str.rs
@@ -6,9 +6,8 @@ use std::ops::{Add, AddAssign, Deref, Range};
use ecow::EcoString;
use unicode_segmentation::UnicodeSegmentation;
-use super::{cast_from_value, dict, Array, Dict, Func, Value, Vm};
+use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm};
use crate::diag::{At, SourceResult, StrResult};
-use crate::eval::Args;
use crate::geom::GenAlign;
/// Create a new [`Str`] from a format string.
@@ -41,8 +40,8 @@ impl Str {
}
/// The length of the string in bytes.
- pub fn len(&self) -> i64 {
- self.0.len() as i64
+ pub fn len(&self) -> usize {
+ self.0.len()
}
/// A string slice containing the entire string.
@@ -82,7 +81,7 @@ impl Str {
/// Extract a contiguous substring.
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
let start = self.locate(start)?;
- let end = self.locate(end.unwrap_or(self.len()))?.max(start);
+ let end = self.locate(end.unwrap_or(self.len() as i64))?.max(start);
Ok(self.0[start..end].into())
}
@@ -283,7 +282,7 @@ impl Str {
match &with {
Replacement::Str(s) => output.push_str(s),
Replacement::Func(func) => {
- let args = Args::new(func.span(), [dict.into()]);
+ let args = Args::new(func.span(), [dict]);
let piece = func.call_vm(vm, args)?.cast::<Str>().at(func.span())?;
output.push_str(&piece);
}
@@ -329,7 +328,7 @@ impl Str {
/// Errors on invalid char boundaries.
fn locate_opt(&self, index: i64) -> StrResult<Option<usize>> {
let wrapped =
- if index >= 0 { Some(index) } else { self.len().checked_add(index) };
+ if index >= 0 { Some(index) } else { (self.len() as i64).checked_add(index) };
let resolved = wrapped
.and_then(|v| usize::try_from(v).ok())
@@ -351,13 +350,13 @@ impl Str {
/// The out of bounds access error message.
#[cold]
-fn out_of_bounds(index: i64, len: i64) -> EcoString {
+fn out_of_bounds(index: i64, len: usize) -> EcoString {
eco_format!("string index out of bounds (index: {}, len: {})", index, len)
}
/// The out of bounds access error message when no default value was given.
#[cold]
-fn no_default_and_out_of_bounds(index: i64, len: i64) -> EcoString {
+fn no_default_and_out_of_bounds(index: i64, len: usize) -> EcoString {
eco_format!("no default value was specified and string index out of bounds (index: {}, len: {})", index, len)
}
@@ -376,10 +375,10 @@ fn string_is_empty() -> EcoString {
/// Convert an item of std's `match_indices` to a dictionary.
fn match_to_dict((start, text): (usize, &str)) -> Dict {
dict! {
- "start" => Value::Int(start as i64),
- "end" => Value::Int((start + text.len()) as i64),
- "text" => Value::Str(text.into()),
- "captures" => Value::Array(Array::new()),
+ "start" => start,
+ "end" => start + text.len(),
+ "text" => text,
+ "captures" => Array::new(),
}
}
@@ -387,15 +386,13 @@ fn match_to_dict((start, text): (usize, &str)) -> Dict {
fn captures_to_dict(cap: regex::Captures) -> Dict {
let m = cap.get(0).expect("missing first match");
dict! {
- "start" => Value::Int(m.start() as i64),
- "end" => Value::Int(m.end() as i64),
- "text" => Value::Str(m.as_str().into()),
- "captures" => Value::Array(
- cap.iter()
- .skip(1)
- .map(|opt| opt.map_or(Value::None, |m| m.as_str().into()))
- .collect(),
- ),
+ "start" => m.start(),
+ "end" => m.end(),
+ "text" => m.as_str(),
+ "captures" => cap.iter()
+ .skip(1)
+ .map(|opt| opt.map_or(Value::None, |m| m.as_str().into_value()))
+ .collect::<Array>(),
}
}
@@ -503,6 +500,35 @@ impl From<Str> for String {
}
}
+cast! {
+ char,
+ self => Value::Str(self.into()),
+ string: Str => {
+ let mut chars = string.chars();
+ match (chars.next(), chars.next()) {
+ (Some(c), None) => c,
+ _ => Err("expected exactly one character")?,
+ }
+ },
+}
+
+cast! {
+ &str,
+ self => Value::Str(self.into()),
+}
+
+cast! {
+ EcoString,
+ self => Value::Str(self.into()),
+ v: Str => v.into(),
+}
+
+cast! {
+ String,
+ self => Value::Str(self.into()),
+ v: Str => v.into(),
+}
+
/// A regular expression.
#[derive(Clone)]
pub struct Regex(regex::Regex);
@@ -540,8 +566,8 @@ impl Hash for Regex {
}
}
-cast_from_value! {
- Regex: "regex",
+cast! {
+ type Regex: "regex",
}
/// A pattern which can be searched for in a string.
@@ -553,7 +579,7 @@ pub enum StrPattern {
Regex(Regex),
}
-cast_from_value! {
+cast! {
StrPattern,
text: Str => Self::Str(text),
regex: Regex => Self::Regex(regex),
@@ -569,7 +595,7 @@ pub enum StrSide {
End,
}
-cast_from_value! {
+cast! {
StrSide,
align: GenAlign => match align {
GenAlign::Start => Self::Start,
@@ -587,7 +613,7 @@ pub enum Replacement {
Func(Func),
}
-cast_from_value! {
+cast! {
Replacement,
text: Str => Self::Str(text),
func: Func => Self::Func(func)
diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs
index 5c8951b1..8f4c93f8 100644
--- a/src/eval/symbol.rs
+++ b/src/eval/symbol.rs
@@ -7,9 +7,6 @@ use ecow::EcoString;
use crate::diag::StrResult;
-#[doc(inline)]
-pub use typst_macros::symbols;
-
/// A symbol, possibly with variants.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct Symbol(Repr);
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 6d947866..91fdadbe 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -8,12 +8,12 @@ use ecow::eco_format;
use siphasher::sip128::{Hasher128, SipHasher13};
use super::{
- cast_to_value, format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func,
- Label, Module, Str, Symbol,
+ cast, format_str, ops, Args, Array, CastInfo, Content, Dict, FromValue, Func,
+ IntoValue, Module, Reflect, Str, Symbol,
};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel};
-use crate::model::Styles;
+use crate::model::{Label, Styles};
use crate::syntax::{ast, Span};
/// A computational value.
@@ -79,11 +79,11 @@ impl Value {
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(),
+ ast::Unit::Length(unit) => Abs::with_unit(v, unit).into_value(),
+ ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into_value(),
+ ast::Unit::Em => Em::new(v).into_value(),
+ ast::Unit::Fr => Fr::new(v).into_value(),
+ ast::Unit::Percent => Ratio::new(v / 100.0).into_value(),
}
}
@@ -116,8 +116,8 @@ impl Value {
}
/// Try to cast the value into a specific type.
- pub fn cast<T: Cast>(self) -> StrResult<T> {
- T::cast(self)
+ pub fn cast<T: FromValue>(self) -> StrResult<T> {
+ T::from_value(self)
}
/// Try to access a field on the value.
@@ -283,8 +283,9 @@ impl PartialEq for Dynamic {
}
}
-cast_to_value! {
- v: Dynamic => Value::Dyn(v)
+cast! {
+ Dynamic,
+ self => Value::Dyn(self),
}
trait Bounds: Debug + Sync + Send + 'static {
@@ -337,20 +338,32 @@ pub trait Type {
/// Implement traits for primitives.
macro_rules! primitive {
(
- $type:ty: $name:literal, $variant:ident
+ $ty:ty: $name:literal, $variant:ident
$(, $other:ident$(($binding:ident))? => $out:expr)*
) => {
- impl Type for $type {
+ impl Type for $ty {
const TYPE_NAME: &'static str = $name;
}
- impl Cast for $type {
- fn is(value: &Value) -> bool {
+ impl Reflect for $ty {
+ fn describe() -> CastInfo {
+ CastInfo::Type(Self::TYPE_NAME)
+ }
+
+ fn castable(value: &Value) -> bool {
matches!(value, Value::$variant(_)
$(| primitive!(@$other $(($binding))?))*)
}
+ }
- fn cast(value: Value) -> StrResult<Self> {
+ impl IntoValue for $ty {
+ fn into_value(self) -> Value {
+ Value::$variant(self)
+ }
+ }
+
+ impl FromValue for $ty {
+ fn from_value(value: Value) -> StrResult<Self> {
match value {
Value::$variant(v) => Ok(v),
$(Value::$other$(($binding))? => Ok($out),)*
@@ -361,16 +374,6 @@ macro_rules! primitive {
)),
}
}
-
- fn describe() -> CastInfo {
- CastInfo::Type(Self::TYPE_NAME)
- }
- }
-
- impl From<$type> for Value {
- fn from(v: $type) -> Self {
- Value::$variant(v)
- }
}
};
@@ -408,8 +411,8 @@ primitive! { Styles: "styles", Styles }
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
primitive! { Func: "function", Func }
-primitive! { Module: "module", Module }
primitive! { Args: "arguments", Args }
+primitive! { Module: "module", Module }
#[cfg(test)]
mod tests {
@@ -418,8 +421,8 @@ mod tests {
use crate::geom::RgbaColor;
#[track_caller]
- fn test(value: impl Into<Value>, exp: &str) {
- assert_eq!(format!("{:?}", value.into()), exp);
+ fn test(value: impl IntoValue, exp: &str) {
+ assert_eq!(format!("{:?}", value.into_value()), exp);
}
#[test]