summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
commit25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch)
tree2fbb4650903123da047a1f1f11a0abda95286e12 /src/eval
parent6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff)
Fully untyped model
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/cast.rs474
-rw-r--r--src/eval/func.rs2
-rw-r--r--src/eval/library.rs2
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/eval/str.rs10
-rw-r--r--src/eval/value.rs68
6 files changed, 156 insertions, 402 deletions
diff --git a/src/eval/cast.rs b/src/eval/cast.rs
index 77521f7f..840ceb05 100644
--- a/src/eval/cast.rs
+++ b/src/eval/cast.rs
@@ -1,18 +1,12 @@
+pub use typst_macros::{cast_from_value, cast_to_value};
+
use std::num::NonZeroUsize;
use std::ops::Add;
-use std::str::FromStr;
use ecow::EcoString;
-use super::{castable, Array, Dict, Func, Regex, Str, Value};
+use super::{Array, Str, Value};
use crate::diag::StrResult;
-use crate::doc::{Destination, Lang, Location, Region};
-use crate::font::{FontStretch, FontStyle, FontWeight};
-use crate::geom::{
- Axes, Color, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Ratio,
- Rel, Sides, Smart,
-};
-use crate::model::{Content, Label, Selector, Transform};
use crate::syntax::Spanned;
/// Cast from a value to a specific type.
@@ -32,88 +26,6 @@ pub trait Cast<V = Value>: Sized {
}
}
-/// Describes a possible value for a cast.
-#[derive(Debug, Clone, Hash)]
-pub enum CastInfo {
- /// Any value is okay.
- Any,
- /// A specific value, plus short documentation for that value.
- Value(Value, &'static str),
- /// Any value of a type.
- Type(&'static str),
- /// Multiple alternatives.
- Union(Vec<Self>),
-}
-
-impl CastInfo {
- /// Produce an error message describing what was expected and what was
- /// found.
- pub fn error(&self, found: &Value) -> EcoString {
- fn accumulate(
- info: &CastInfo,
- found: &Value,
- parts: &mut Vec<EcoString>,
- matching_type: &mut bool,
- ) {
- match info {
- CastInfo::Any => parts.push("anything".into()),
- CastInfo::Value(value, _) => {
- parts.push(value.repr().into());
- if value.type_name() == found.type_name() {
- *matching_type = true;
- }
- }
- CastInfo::Type(ty) => parts.push((*ty).into()),
- CastInfo::Union(options) => {
- for option in options {
- accumulate(option, found, parts, matching_type);
- }
- }
- }
- }
-
- let mut matching_type = false;
- let mut parts = vec![];
- accumulate(self, found, &mut parts, &mut matching_type);
-
- let mut msg = String::from("expected ");
- if parts.is_empty() {
- msg.push_str(" nothing");
- }
-
- crate::diag::comma_list(&mut msg, &parts, "or");
-
- if !matching_type {
- msg.push_str(", found ");
- msg.push_str(found.type_name());
- }
-
- msg.into()
- }
-}
-
-impl Add for CastInfo {
- type Output = Self;
-
- fn add(self, rhs: Self) -> Self {
- Self::Union(match (self, rhs) {
- (Self::Union(mut lhs), Self::Union(rhs)) => {
- lhs.extend(rhs);
- lhs
- }
- (Self::Union(mut lhs), rhs) => {
- lhs.push(rhs);
- lhs
- }
- (lhs, Self::Union(mut rhs)) => {
- rhs.insert(0, lhs);
- rhs
- }
- (lhs, rhs) => vec![lhs, rhs],
- })
- }
-}
-
impl Cast for Value {
fn is(_: &Value) -> bool {
true
@@ -157,43 +69,15 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
}
}
-castable! {
- Dir: "direction",
-}
-
-castable! {
- GenAlign: "alignment",
-}
-
-castable! {
- Regex: "regular expression",
+cast_to_value! {
+ v: u8 => Value::Int(v as i64)
}
-castable! {
- Selector: "selector",
- text: EcoString => Self::text(&text),
- label: Label => Self::Label(label),
- func: Func => func.select(None)?,
- regex: Regex => Self::Regex(regex),
+cast_to_value! {
+ v: u16 => Value::Int(v as i64)
}
-castable! {
- Axes<GenAlign>: "2d alignment",
-}
-
-castable! {
- PartialStroke: "stroke",
- thickness: Length => Self {
- paint: Smart::Auto,
- thickness: Smart::Custom(thickness),
- },
- color: Color => Self {
- paint: Smart::Custom(color.into()),
- thickness: Smart::Auto,
- },
-}
-
-castable! {
+cast_from_value! {
u32,
int: i64 => int.try_into().map_err(|_| {
if int < 0 {
@@ -204,7 +88,15 @@ castable! {
})?,
}
-castable! {
+cast_to_value! {
+ v: u32 => Value::Int(v as i64)
+}
+
+cast_to_value! {
+ v: i32 => Value::Int(v as i64)
+}
+
+cast_from_value! {
usize,
int: i64 => int.try_into().map_err(|_| {
if int < 0 {
@@ -215,7 +107,11 @@ castable! {
})?,
}
-castable! {
+cast_to_value! {
+ v: usize => Value::Int(v as i64)
+}
+
+cast_from_value! {
NonZeroUsize,
int: i64 => int
.try_into()
@@ -227,12 +123,11 @@ castable! {
})?,
}
-castable! {
- Paint,
- color: Color => Self::Solid(color),
+cast_to_value! {
+ v: NonZeroUsize => Value::Int(v.get() as i64)
}
-castable! {
+cast_from_value! {
char,
string: Str => {
let mut chars = string.chars();
@@ -243,131 +138,30 @@ castable! {
},
}
-castable! {
- EcoString,
- string: Str => string.into(),
-}
-
-castable! {
- String,
- string: Str => string.into(),
-}
-
-castable! {
- Transform,
- content: Content => Self::Content(content),
- func: Func => {
- if func.argc().map_or(false, |count| count != 1) {
- Err("function must have exactly one parameter")?
- }
- Self::Func(func)
- },
-}
-
-castable! {
- Axes<Option<GenAlign>>,
- align: GenAlign => {
- let mut aligns = Axes::default();
- aligns.set(align.axis(), Some(align));
- aligns
- },
- aligns: Axes<GenAlign> => aligns.map(Some),
-}
-
-castable! {
- Axes<Rel<Length>>,
- array: Array => {
- let mut iter = array.into_iter();
- match (iter.next(), iter.next(), iter.next()) {
- (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
- _ => Err("point array must contain exactly two entries")?,
- }
- },
-}
-
-castable! {
- Location,
- mut dict: Dict => {
- let page = dict.take("page")?.cast()?;
- let x: Length = dict.take("x")?.cast()?;
- let y: Length = dict.take("y")?.cast()?;
- dict.finish(&["page", "x", "y"])?;
- Self { page, pos: Point::new(x.abs, y.abs) }
- },
-}
-
-castable! {
- Destination,
- loc: Location => Self::Internal(loc),
- string: EcoString => Self::Url(string),
+cast_to_value! {
+ v: char => Value::Str(v.into())
}
-castable! {
- FontStyle,
- /// The default, typically upright style.
- "normal" => Self::Normal,
- /// A cursive style with custom letterform.
- "italic" => Self::Italic,
- /// Just a slanted version of the normal style.
- "oblique" => Self::Oblique,
+cast_to_value! {
+ v: &str => Value::Str(v.into())
}
-castable! {
- FontWeight,
- v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
- /// Thin weight (100).
- "thin" => Self::THIN,
- /// Extra light weight (200).
- "extralight" => Self::EXTRALIGHT,
- /// Light weight (300).
- "light" => Self::LIGHT,
- /// Regular weight (400).
- "regular" => Self::REGULAR,
- /// Medium weight (500).
- "medium" => Self::MEDIUM,
- /// Semibold weight (600).
- "semibold" => Self::SEMIBOLD,
- /// Bold weight (700).
- "bold" => Self::BOLD,
- /// Extrabold weight (800).
- "extrabold" => Self::EXTRABOLD,
- /// Black weight (900).
- "black" => Self::BLACK,
+cast_from_value! {
+ EcoString,
+ v: Str => v.into(),
}
-castable! {
- FontStretch,
- v: Ratio => Self::from_ratio(v.get() as f32),
+cast_to_value! {
+ v: EcoString => Value::Str(v.into())
}
-castable! {
- Lang,
- string: EcoString => Self::from_str(&string)?,
+cast_from_value! {
+ String,
+ v: Str => v.into(),
}
-castable! {
- Region,
- string: EcoString => Self::from_str(&string)?,
-}
-
-/// Castable from [`Value::None`].
-pub struct NoneValue;
-
-impl Cast for NoneValue {
- fn is(value: &Value) -> bool {
- matches!(value, Value::None)
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::None => Ok(Self),
- _ => <Self as Cast>::error(value),
- }
- }
-
- fn describe() -> CastInfo {
- CastInfo::Type("none")
- }
+cast_to_value! {
+ v: String => Value::Str(v.into())
}
impl<T: Cast> Cast for Option<T> {
@@ -388,126 +182,130 @@ impl<T: Cast> Cast for Option<T> {
}
}
-/// Castable from [`Value::Auto`].
-pub struct AutoValue;
+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 Cast for AutoValue {
+impl<T: Cast> Cast for Vec<T> {
fn is(value: &Value) -> bool {
- matches!(value, Value::Auto)
+ Array::is(value)
}
fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::Auto => Ok(Self),
- _ => <Self as Cast>::error(value),
- }
+ value.cast::<Array>()?.into_iter().map(Value::cast).collect()
}
fn describe() -> CastInfo {
- CastInfo::Type("auto")
+ <Array as Cast>::describe()
}
}
-impl<T: Cast> Cast for Smart<T> {
- fn is(value: &Value) -> bool {
- matches!(value, Value::Auto) || T::is(value)
+impl<T: Into<Value>> From<Vec<T>> for Value {
+ fn from(v: Vec<T>) -> Self {
+ Value::Array(v.into_iter().map(Into::into).collect())
}
+}
- fn cast(value: Value) -> StrResult<Self> {
- match value {
- Value::Auto => Ok(Self::Auto),
- v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)),
- _ => <Self as Cast>::error(value),
+/// Describes a possible value for a cast.
+#[derive(Debug, Clone, Hash)]
+pub enum CastInfo {
+ /// Any value is okay.
+ Any,
+ /// A specific value, plus short documentation for that value.
+ Value(Value, &'static str),
+ /// Any value of a type.
+ Type(&'static str),
+ /// Multiple alternatives.
+ Union(Vec<Self>),
+}
+
+impl CastInfo {
+ /// Produce an error message describing what was expected and what was
+ /// found.
+ pub fn error(&self, found: &Value) -> EcoString {
+ fn accumulate(
+ info: &CastInfo,
+ found: &Value,
+ parts: &mut Vec<EcoString>,
+ matching_type: &mut bool,
+ ) {
+ match info {
+ CastInfo::Any => parts.push("anything".into()),
+ CastInfo::Value(value, _) => {
+ parts.push(value.repr().into());
+ if value.type_name() == found.type_name() {
+ *matching_type = true;
+ }
+ }
+ CastInfo::Type(ty) => parts.push((*ty).into()),
+ CastInfo::Union(options) => {
+ for option in options {
+ accumulate(option, found, parts, matching_type);
+ }
+ }
+ }
}
- }
- fn describe() -> CastInfo {
- T::describe() + CastInfo::Type("auto")
- }
-}
+ let mut matching_type = false;
+ let mut parts = vec![];
+ accumulate(self, found, &mut parts, &mut matching_type);
-impl<T> Cast for Sides<Option<T>>
-where
- T: Cast + Copy,
-{
- fn is(value: &Value) -> bool {
- matches!(value, Value::Dict(_)) || T::is(value)
- }
+ let mut msg = String::from("expected ");
+ if parts.is_empty() {
+ msg.push_str(" nothing");
+ }
- fn cast(mut value: Value) -> StrResult<Self> {
- if let Value::Dict(dict) = &mut value {
- let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
-
- let rest = take("rest")?;
- let x = take("x")?.or(rest);
- let y = take("y")?.or(rest);
- let sides = Sides {
- left: take("left")?.or(x),
- top: take("top")?.or(y),
- right: take("right")?.or(x),
- bottom: take("bottom")?.or(y),
- };
-
- dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?;
-
- Ok(sides)
- } else if T::is(&value) {
- Ok(Self::splat(Some(T::cast(value)?)))
- } else {
- <Self as Cast>::error(value)
+ crate::diag::comma_list(&mut msg, &parts, "or");
+
+ if !matching_type {
+ msg.push_str(", found ");
+ msg.push_str(found.type_name());
}
+
+ msg.into()
}
+}
- fn describe() -> CastInfo {
- T::describe() + CastInfo::Type("dictionary")
+impl Add for CastInfo {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self {
+ Self::Union(match (self, rhs) {
+ (Self::Union(mut lhs), Self::Union(rhs)) => {
+ lhs.extend(rhs);
+ lhs
+ }
+ (Self::Union(mut lhs), rhs) => {
+ lhs.push(rhs);
+ lhs
+ }
+ (lhs, Self::Union(mut rhs)) => {
+ rhs.insert(0, lhs);
+ rhs
+ }
+ (lhs, rhs) => vec![lhs, rhs],
+ })
}
}
-impl<T> Cast for Corners<Option<T>>
-where
- T: Cast + Copy,
-{
- fn is(value: &Value) -> bool {
- matches!(value, Value::Dict(_)) || T::is(value)
+/// Castable from nothing.
+pub enum Never {}
+
+impl Cast for Never {
+ fn is(_: &Value) -> bool {
+ false
}
- fn cast(mut value: Value) -> StrResult<Self> {
- if let Value::Dict(dict) = &mut value {
- let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
-
- let rest = take("rest")?;
- let left = take("left")?.or(rest);
- let top = take("top")?.or(rest);
- let right = take("right")?.or(rest);
- let bottom = take("bottom")?.or(rest);
- let corners = Corners {
- top_left: take("top-left")?.or(top).or(left),
- top_right: take("top-right")?.or(top).or(right),
- bottom_right: take("bottom-right")?.or(bottom).or(right),
- bottom_left: take("bottom-left")?.or(bottom).or(left),
- };
-
- dict.finish(&[
- "top-left",
- "top-right",
- "bottom-right",
- "bottom-left",
- "left",
- "top",
- "right",
- "bottom",
- "rest",
- ])?;
-
- Ok(corners)
- } else if T::is(&value) {
- Ok(Self::splat(Some(T::cast(value)?)))
- } else {
- <Self as Cast>::error(value)
- }
+ fn cast(value: Value) -> StrResult<Self> {
+ <Self as Cast>::error(value)
}
fn describe() -> CastInfo {
- T::describe() + CastInfo::Type("dictionary")
+ CastInfo::Union(vec![])
}
}
diff --git a/src/eval/func.rs b/src/eval/func.rs
index e5280932..8243b4f6 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -1,3 +1,5 @@
+pub use typst_macros::func;
+
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
diff --git a/src/eval/library.rs b/src/eval/library.rs
index adfcc6e7..75787348 100644
--- a/src/eval/library.rs
+++ b/src/eval/library.rs
@@ -44,7 +44,7 @@ pub struct LangItems {
/// The id of the text node.
pub text_id: NodeId,
/// Get the string if this is a text node.
- pub text_str: fn(&Content) -> Option<&str>,
+ pub text_str: fn(&Content) -> Option<EcoString>,
/// A smart quote: `'` or `"`.
pub smart_quote: fn(double: bool) -> Content,
/// A paragraph break.
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 2cf6f4d1..8180f11d 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -20,8 +20,6 @@ mod ops;
mod scope;
mod symbol;
-pub use typst_macros::{castable, func};
-
pub use self::args::*;
pub use self::array::*;
pub use self::cast::*;
diff --git a/src/eval/str.rs b/src/eval/str.rs
index 63ea5dc8..0d5d71b9 100644
--- a/src/eval/str.rs
+++ b/src/eval/str.rs
@@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign, Deref};
use ecow::EcoString;
use unicode_segmentation::UnicodeSegmentation;
-use super::{castable, dict, Array, Dict, Value};
+use super::{cast_from_value, dict, Array, Dict, Value};
use crate::diag::StrResult;
use crate::geom::GenAlign;
@@ -479,6 +479,10 @@ impl Hash for Regex {
}
}
+cast_from_value! {
+ Regex: "regular expression",
+}
+
/// A pattern which can be searched for in a string.
#[derive(Debug, Clone)]
pub enum StrPattern {
@@ -488,7 +492,7 @@ pub enum StrPattern {
Regex(Regex),
}
-castable! {
+cast_from_value! {
StrPattern,
text: Str => Self::Str(text),
regex: Regex => Self::Regex(regex),
@@ -504,7 +508,7 @@ pub enum StrSide {
End,
}
-castable! {
+cast_from_value! {
StrSide,
align: GenAlign => match align {
GenAlign::Start => Self::Start,
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 5e06da76..9b9bc314 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,15 +4,15 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
-use ecow::{eco_format, EcoString};
+use ecow::eco_format;
use siphasher::sip128::{Hasher128, SipHasher};
use super::{
- format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module,
- Str, Symbol,
+ cast_to_value, 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::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel};
use crate::syntax::{ast, Span};
/// A computational value.
@@ -122,6 +122,7 @@ impl Value {
Self::Dict(dict) => dict.at(&field).cloned(),
Self::Content(content) => content
.field(&field)
+ .cloned()
.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())),
@@ -241,60 +242,6 @@ impl Hash for Value {
}
}
-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>);
@@ -336,6 +283,10 @@ impl PartialEq for Dynamic {
}
}
+cast_to_value! {
+ v: Dynamic => Value::Dyn(v)
+}
+
trait Bounds: Debug + Sync + Send + 'static {
fn as_any(&self) -> &dyn Any;
fn dyn_eq(&self, other: &Dynamic) -> bool;
@@ -462,6 +413,7 @@ primitive! { Args: "arguments", Args }
mod tests {
use super::*;
use crate::eval::{array, dict};
+ use crate::geom::RgbaColor;
#[track_caller]
fn test(value: impl Into<Value>, exp: &str) {