From 2b660968aa7e1e8efb7c396e17066a1a98c8c10e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 21 Dec 2020 00:36:22 +0100 Subject: [PATCH] =?UTF-8?q?Restructure=20value=20conversions=20?= =?UTF-8?q?=F0=9F=A7=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/color.rs | 2 +- src/eval/args.rs | 102 ++++++++++++++----------- src/eval/convert.rs | 170 ------------------------------------------ src/eval/mod.rs | 4 +- src/eval/value.rs | 165 +++++++++++++++++++++++++++++++++++++++- src/library/layout.rs | 2 +- src/main.rs | 2 +- src/prelude.rs | 2 +- 8 files changed, 226 insertions(+), 223 deletions(-) delete mode 100644 src/eval/convert.rs diff --git a/src/color.rs b/src/color.rs index 427557d2b..b7266e1b9 100644 --- a/src/color.rs +++ b/src/color.rs @@ -99,7 +99,7 @@ impl Debug for RgbaColor { } } -/// The error when parsing an `RgbaColor` fails. +/// The error when parsing an [`RgbaColor`] fails. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ParseColorError; diff --git a/src/eval/args.rs b/src/eval/args.rs index e9bf378bc..765ae09c5 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 where K: Into>, - 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 where K: Into>, - 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(&mut self) -> Option 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(&mut self) -> impl Iterator + '_ 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(&mut self) -> impl Iterator + '_ 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(conv: Conv, diags: &mut SpanVec, span: Span) -> Option { + 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(conv: Conv, slot: &mut Spanned, span: Span) -> Option { + 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 81e426941..000000000 --- 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` instead of just a `Value` so that - /// this trait can be blanket implemented for `Spanned` where `T: - /// Convert`. - fn convert(value: Spanned) -> (Result, Option); -} - -impl Convert for Spanned { - fn convert(value: Spanned) -> (Result, Option) { - 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, 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, - 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 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) -> (Result, Option) { - 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 bab93a058..c2b80b68f 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 e897fa75e..8c98257e3 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("") } } + +/// 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) -> Conv; +} + +/// The result of a conversion. +#[derive(Debug, Clone, PartialEq)] +pub enum Conv { + /// 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 Conv { + /// Map the conversion result. + pub fn map(self, f: impl FnOnce(T) -> U) -> Conv { + 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 TryFromValue for Spanned { + fn try_from_value(value: Spanned) -> Conv { + 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 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) -> $crate::eval::Conv { + 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) -> $crate::eval::Conv { + 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) -> Conv { + 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) + } + } + } +} diff --git a/src/library/layout.rs b/src/library/layout.rs index 23066fdc4..43826e245 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -116,7 +116,7 @@ enum SpecAlign { Center, } -convert_ident!(SpecAlign, "alignment", |v| match v { +try_from_id!(SpecAlign["alignment"]: |v| match v { "left" => Some(Self::Left), "right" => Some(Self::Right), "top" => Some(Self::Top), diff --git a/src/main.rs b/src/main.rs index acd2d0cd1..213734d77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,7 @@ fn main() -> anyhow::Result<()> { let mut index = FsIndex::new(); index.search_dir("fonts"); - index.search_os(); + index.search_system(); let (files, descriptors) = index.into_vecs(); let env = Rc::new(RefCell::new(Env { diff --git a/src/prelude.rs b/src/prelude.rs index 16b598ceb..0c28dd202 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -7,5 +7,5 @@ pub use crate::geom::*; #[doc(no_inline)] pub use crate::layout::LayoutNode; #[doc(no_inline)] -pub use crate::syntax::{Span, Spanned, SynTree}; +pub use crate::syntax::{Span, SpanWith, Spanned, SynTree}; pub use crate::{error, warning};