summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-12-21 00:36:22 +0100
committerLaurenz <laurmaedje@gmail.com>2020-12-21 00:36:22 +0100
commit2b660968aa7e1e8efb7c396e17066a1a98c8c10e (patch)
tree40a059ccad2c82455af1d8456fabcb4502491642 /src/eval
parent6f111f941008f10ddc06e6f56da9e3582e90d2c4 (diff)
Restructure value conversions 🧱
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs102
-rw-r--r--src/eval/convert.rs170
-rw-r--r--src/eval/mod.rs4
-rw-r--r--src/eval/value.rs165
4 files changed, 222 insertions, 219 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
index e9bf378b..765ae09c 100644
--- a/src/eval/args.rs
+++ b/src/eval/args.rs
@@ -1,7 +1,10 @@
//! Simplifies argument parsing.
-use super::{Convert, EvalContext, RefKey, ValueDict};
-use crate::syntax::{SpanWith, Spanned};
+use std::mem;
+
+use super::{Conv, EvalContext, RefKey, TryFromValue, Value, ValueDict};
+use crate::diag::Diag;
+use crate::syntax::{Span, SpanVec, SpanWith, Spanned};
/// A wrapper around a dictionary value that simplifies argument parsing in
/// functions.
@@ -16,15 +19,11 @@ impl Args {
pub fn get<'a, K, T>(&mut self, ctx: &mut EvalContext, key: K) -> Option<T>
where
K: Into<RefKey<'a>>,
- T: Convert,
+ T: TryFromValue,
{
self.0.v.remove(key).and_then(|entry| {
let span = entry.value.span;
- let (result, diag) = T::convert(entry.value);
- if let Some(diag) = diag {
- ctx.f.diags.push(diag.span_with(span))
- }
- result.ok()
+ conv_diag(T::try_from_value(entry.value), &mut ctx.f.diags, span)
})
}
@@ -38,37 +37,29 @@ impl Args {
) -> Option<T>
where
K: Into<RefKey<'a>>,
- T: Convert,
+ T: TryFromValue,
{
- match self.0.v.remove(key) {
- Some(entry) => {
- let span = entry.value.span;
- let (result, diag) = T::convert(entry.value);
- if let Some(diag) = diag {
- ctx.f.diags.push(diag.span_with(span))
- }
- result.ok()
- }
- None => {
- ctx.f.diags.push(error!(self.0.span, "missing argument: {}", name));
- None
- }
+ if let Some(entry) = self.0.v.remove(key) {
+ let span = entry.value.span;
+ conv_diag(T::try_from_value(entry.value), &mut ctx.f.diags, span)
+ } else {
+ ctx.f.diags.push(error!(self.0.span, "missing argument: {}", name));
+ None
}
}
/// Retrieve and remove the first matching positional argument.
pub fn find<T>(&mut self) -> Option<T>
where
- T: Convert,
+ T: TryFromValue,
{
for (&key, entry) in self.0.v.nums_mut() {
let span = entry.value.span;
- match T::convert(std::mem::take(&mut entry.value)).0 {
- Ok(t) => {
- self.0.v.remove(key);
- return Some(t);
- }
- Err(v) => entry.value = v.span_with(span),
+ let slot = &mut entry.value;
+ let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
+ if let Some(t) = conv {
+ self.0.v.remove(key);
+ return Some(t);
}
}
None
@@ -77,18 +68,17 @@ impl Args {
/// Retrieve and remove all matching positional arguments.
pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_
where
- T: Convert,
+ T: TryFromValue,
{
let mut skip = 0;
std::iter::from_fn(move || {
for (&key, entry) in self.0.v.nums_mut().skip(skip) {
let span = entry.value.span;
- match T::convert(std::mem::take(&mut entry.value)).0 {
- Ok(t) => {
- self.0.v.remove(key);
- return Some(t);
- }
- Err(v) => entry.value = v.span_with(span),
+ let slot = &mut entry.value;
+ let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
+ if let Some(t) = conv {
+ self.0.v.remove(key);
+ return Some(t);
}
skip += 1;
}
@@ -99,19 +89,18 @@ impl Args {
/// Retrieve and remove all matching keyword arguments.
pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
where
- T: Convert,
+ T: TryFromValue,
{
let mut skip = 0;
std::iter::from_fn(move || {
for (key, entry) in self.0.v.strs_mut().skip(skip) {
let span = entry.value.span;
- match T::convert(std::mem::take(&mut entry.value)).0 {
- Ok(t) => {
- let key = key.clone();
- self.0.v.remove(&key);
- return Some((key, t));
- }
- Err(v) => entry.value = v.span_with(span),
+ let slot = &mut entry.value;
+ let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
+ if let Some(t) = conv {
+ let key = key.clone();
+ self.0.v.remove(&key);
+ return Some((key, t));
}
skip += 1;
}
@@ -129,6 +118,31 @@ impl Args {
}
}
+fn conv_diag<T>(conv: Conv<T>, diags: &mut SpanVec<Diag>, span: Span) -> Option<T> {
+ match conv {
+ Conv::Ok(t) => Some(t),
+ Conv::Warn(t, warn) => {
+ diags.push(warn.span_with(span));
+ Some(t)
+ }
+ Conv::Err(_, err) => {
+ diags.push(err.span_with(span));
+ None
+ }
+ }
+}
+
+fn conv_put_back<T>(conv: Conv<T>, slot: &mut Spanned<Value>, span: Span) -> Option<T> {
+ match conv {
+ Conv::Ok(t) => Some(t),
+ Conv::Warn(t, _) => Some(t),
+ Conv::Err(v, _) => {
+ *slot = v.span_with(span);
+ None
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::super::{Dict, SpannedEntry, Value};
diff --git a/src/eval/convert.rs b/src/eval/convert.rs
deleted file mode 100644
index 81e42694..00000000
--- a/src/eval/convert.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-//! Conversion from values into other types.
-
-use std::ops::Deref;
-
-use fontdock::{FontStretch, FontStyle, FontWeight};
-
-use super::{Value, ValueDict, ValueFunc};
-use crate::diag::Diag;
-use crate::geom::{Dir, Length, Linear, Relative};
-use crate::paper::Paper;
-use crate::syntax::{Ident, SpanWith, Spanned, SynTree};
-
-/// Types that values can be converted into.
-pub trait Convert: Sized {
- /// Convert a value into `Self`.
- ///
- /// If the conversion works out, this should return `Ok(...)` with an
- /// instance of `Self`. If it doesn't, it should return `Err(...)` giving
- /// back the original value.
- ///
- /// In addition to the result, the method can return an optional diagnostic
- /// to warn even when the conversion succeeded or to explain the problem when
- /// the conversion failed.
- ///
- /// The function takes a `Spanned<Value>` instead of just a `Value` so that
- /// this trait can be blanket implemented for `Spanned<T>` where `T:
- /// Convert`.
- fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>);
-}
-
-impl<T: Convert> Convert for Spanned<T> {
- fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
- let span = value.span;
- let (result, diag) = T::convert(value);
- (result.map(|v| v.span_with(span)), diag)
- }
-}
-
-macro_rules! convert_match {
- ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
- impl $crate::eval::Convert for $type {
- fn convert(
- value: $crate::syntax::Spanned<$crate::eval::Value>
- ) -> (Result<Self, $crate::eval::Value>, Option<$crate::diag::Diag>) {
- #[allow(unreachable_patterns)]
- match value.v {
- $($p => (Ok($r), None)),*,
- v => {
- let err = $crate::error!("expected {}, found {}", $name, v.ty());
- (Err(v), Some(err))
- },
- }
- }
- }
- };
-}
-
-macro_rules! convert_ident {
- ($type:ty, $name:expr, $parse:expr) => {
- impl $crate::eval::Convert for $type {
- fn convert(
- value: $crate::syntax::Spanned<$crate::eval::Value>,
- ) -> (
- Result<Self, $crate::eval::Value>,
- Option<$crate::diag::Diag>,
- ) {
- match value.v {
- Value::Ident(id) => {
- if let Some(thing) = $parse(&id) {
- (Ok(thing), None)
- } else {
- (
- Err($crate::eval::Value::Ident(id)),
- Some($crate::error!("invalid {}", $name)),
- )
- }
- }
- v => {
- let err = $crate::error!("expected {}, found {}", $name, v.ty());
- (Err(v), Some(err))
- }
- }
- }
- }
- };
-}
-
-/// A value type that matches [identifier](Value::Ident) and [string](Value::Str) values.
-pub struct StringLike(pub String);
-
-impl From<StringLike> for String {
- fn from(like: StringLike) -> String {
- like.0
- }
-}
-
-impl Deref for StringLike {
- type Target = str;
-
- fn deref(&self) -> &str {
- self.0.as_str()
- }
-}
-
-convert_match!(Value, "value", v => v);
-convert_match!(Ident, "identifier", Value::Ident(v) => v);
-convert_match!(bool, "bool", Value::Bool(v) => v);
-convert_match!(i64, "integer", Value::Int(v) => v);
-convert_match!(f64, "float",
- Value::Int(v) => v as f64,
- Value::Float(v) => v,
-);
-convert_match!(Length, "length", Value::Length(v) => v);
-convert_match!(Relative, "relative", Value::Relative(v) => v);
-convert_match!(Linear, "linear",
- Value::Linear(v) => v,
- Value::Length(v) => v.into(),
- Value::Relative(v) => v.into(),
-);
-convert_match!(String, "string", Value::Str(v) => v);
-convert_match!(SynTree, "tree", Value::Content(v) => v);
-convert_match!(ValueDict, "dictionary", Value::Dict(v) => v);
-convert_match!(ValueFunc, "function", Value::Func(v) => v);
-convert_match!(StringLike, "identifier or string",
- Value::Ident(Ident(v)) => StringLike(v),
- Value::Str(v) => StringLike(v),
-);
-
-convert_ident!(Dir, "direction", |v| match v {
- "ltr" => Some(Self::LTR),
- "rtl" => Some(Self::RTL),
- "ttb" => Some(Self::TTB),
- "btt" => Some(Self::BTT),
- _ => None,
-});
-
-convert_ident!(FontStyle, "font style", Self::from_str);
-convert_ident!(FontStretch, "font stretch", Self::from_str);
-convert_ident!(Paper, "paper", Self::from_name);
-
-impl Convert for FontWeight {
- fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
- match value.v {
- Value::Int(number) => {
- let [min, max] = [100, 900];
- let warning = if number < min {
- Some(warning!("the minimum font weight is {}", min))
- } else if number > max {
- Some(warning!("the maximum font weight is {}", max))
- } else {
- None
- };
- let weight = Self::from_number(number.min(max).max(min) as u16);
- (Ok(weight), warning)
- }
- Value::Ident(id) => {
- if let Some(thing) = FontWeight::from_str(&id) {
- (Ok(thing), None)
- } else {
- (Err(Value::Ident(id)), Some(error!("invalid font weight")))
- }
- }
- v => {
- let err =
- error!("expected font weight (name or number), found {}", v.ty());
- (Err(v), Some(err))
- }
- }
- }
-}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index bab93a05..c2b80b68 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -1,15 +1,13 @@
//! Evaluation of syntax trees.
#[macro_use]
-mod convert;
+mod value;
mod args;
mod dict;
mod scope;
mod state;
-mod value;
pub use args::*;
-pub use convert::*;
pub use dict::*;
pub use scope::*;
pub use state::*;
diff --git a/src/eval/value.rs b/src/eval/value.rs
index e897fa75..8c98257e 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,10 +4,14 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
use std::rc::Rc;
+use fontdock::{FontStretch, FontStyle, FontWeight};
+
use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::RgbaColor;
-use crate::geom::{Length, Linear, Relative};
-use crate::syntax::{Ident, SynTree};
+use crate::diag::Diag;
+use crate::geom::{Dir, Length, Linear, Relative};
+use crate::paper::Paper;
+use crate::syntax::{Ident, SpanWith, Spanned, SynTree};
/// A computational value.
#[derive(Clone, PartialEq)]
@@ -170,3 +174,160 @@ impl Debug for ValueFunc {
f.pad("<function>")
}
}
+
+/// Try to convert a value into a more specific type.
+pub trait TryFromValue: Sized {
+ /// Try to convert the value into yourself.
+ fn try_from_value(value: Spanned<Value>) -> Conv<Self>;
+}
+
+/// The result of a conversion.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Conv<T> {
+ /// Success conversion.
+ Ok(T),
+ /// Sucessful conversion with a warning.
+ Warn(T, Diag),
+ /// Unsucessful conversion, gives back the value alongside the error.
+ Err(Value, Diag),
+}
+
+impl<T> Conv<T> {
+ /// Map the conversion result.
+ pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Conv<U> {
+ match self {
+ Conv::Ok(t) => Conv::Ok(f(t)),
+ Conv::Warn(t, warn) => Conv::Warn(f(t), warn),
+ Conv::Err(v, err) => Conv::Err(v, err),
+ }
+ }
+}
+
+impl<T: TryFromValue> TryFromValue for Spanned<T> {
+ fn try_from_value(value: Spanned<Value>) -> Conv<Self> {
+ let span = value.span;
+ T::try_from_value(value).map(|v| v.span_with(span))
+ }
+}
+
+/// A value type that matches [identifier](Value::Ident) and [string](Value::Str) values.
+pub struct StringLike(pub String);
+
+impl From<StringLike> for String {
+ fn from(like: StringLike) -> String {
+ like.0
+ }
+}
+
+impl Deref for StringLike {
+ type Target = str;
+
+ fn deref(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+/// Implement [`TryFromValue`] through a match.
+macro_rules! try_from_match {
+ ($type:ty[$name:literal] $(@ $span:ident)?: $($pattern:pat => $output:expr),* $(,)?) => {
+ impl $crate::eval::TryFromValue for $type {
+ fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> {
+ use $crate::eval::Conv;
+ #[allow(unused)]
+ $(let $span = value.span;)?
+ #[allow(unreachable_patterns)]
+ match value.v {
+ $($pattern => Conv::Ok($output)),*,
+ v => {
+ let e = error!("expected {}, found {}", $name, v.ty());
+ Conv::Err(v, e)
+ }
+ }
+ }
+ }
+ };
+}
+
+/// Implement [`TryFromValue`] through a function parsing an identifier.
+macro_rules! try_from_id {
+ ($type:ty[$name:literal]: $from_str:expr) => {
+ impl $crate::eval::TryFromValue for $type {
+ fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> {
+ use $crate::eval::Conv;
+ let v = value.v;
+ if let Value::Ident(id) = v {
+ if let Some(v) = $from_str(&id) {
+ Conv::Ok(v)
+ } else {
+ Conv::Err(Value::Ident(id), error!("invalid {}", $name))
+ }
+ } else {
+ let e = error!("expected identifier, found {}", v.ty());
+ Conv::Err(v, e)
+ }
+ }
+ }
+ };
+}
+
+try_from_match!(Value["value"]: v => v);
+try_from_match!(Ident["identifier"]: Value::Ident(v) => v);
+try_from_match!(bool["bool"]: Value::Bool(v) => v);
+try_from_match!(i64["integer"]: Value::Int(v) => v);
+try_from_match!(f64["float"]:
+ Value::Int(v) => v as f64,
+ Value::Float(v) => v,
+);
+try_from_match!(Length["length"]: Value::Length(v) => v);
+try_from_match!(Relative["relative"]: Value::Relative(v) => v);
+try_from_match!(Linear["linear"]:
+ Value::Linear(v) => v,
+ Value::Length(v) => v.into(),
+ Value::Relative(v) => v.into(),
+);
+try_from_match!(String["string"]: Value::Str(v) => v);
+try_from_match!(SynTree["tree"]: Value::Content(v) => v);
+try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v);
+try_from_match!(ValueFunc["function"]: Value::Func(v) => v);
+try_from_match!(StringLike["identifier or string"]:
+ Value::Ident(Ident(v)) => Self(v),
+ Value::Str(v) => Self(v),
+);
+try_from_id!(Dir["direction"]: |v| match v {
+ "ltr" | "left-to-right" => Some(Self::LTR),
+ "rtl" | "right-to-left" => Some(Self::RTL),
+ "ttb" | "top-to-bottom" => Some(Self::TTB),
+ "btt" | "bottom-to-top" => Some(Self::BTT),
+ _ => None,
+});
+try_from_id!(FontStyle["font style"]: Self::from_str);
+try_from_id!(FontStretch["font stretch"]: Self::from_str);
+try_from_id!(Paper["paper"]: Self::from_name);
+
+impl TryFromValue for FontWeight {
+ fn try_from_value(value: Spanned<Value>) -> Conv<Self> {
+ match value.v {
+ Value::Int(number) => {
+ let [min, max] = [Self::THIN, Self::BLACK];
+ if number < i64::from(min.to_number()) {
+ Conv::Warn(min, warning!("the minimum font weight is {:#?}", min))
+ } else if number > i64::from(max.to_number()) {
+ Conv::Warn(max, warning!("the maximum font weight is {:#?}", max))
+ } else {
+ Conv::Ok(Self::from_number(number as u16))
+ }
+ }
+ Value::Ident(id) => {
+ if let Some(weight) = Self::from_str(&id) {
+ Conv::Ok(weight)
+ } else {
+ Conv::Err(Value::Ident(id), error!("invalid font weight"))
+ }
+ }
+ v => {
+ let e = error!("expected font weight, found {}", v.ty());
+ Conv::Err(v, e)
+ }
+ }
+ }
+}