summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-03 15:07:57 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-03 15:07:57 +0200
commit95bae5725cf6495644e2593f8492f1cd0e5bd3c1 (patch)
tree919dd90cac7623bcbbc09d9c92399eaa65e537f2 /src/eval
parent0fc25d732d7cbc37cf801645849d1060f2cec4a3 (diff)
Int, Float, Relative and Linear values 🍉
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/dict.rs30
-rw-r--r--src/eval/value.rs143
2 files changed, 114 insertions, 59 deletions
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index e6216572..7f4426ff 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -6,8 +6,8 @@ use std::ops::Index;
use crate::syntax::{Span, Spanned};
-/// A dictionary data structure, which maps from integers (`u64`) or strings to
-/// a generic value type.
+/// A dictionary data structure, which maps from integers and strings to a
+/// generic value type.
///
/// The dictionary can be used to model arrays by assigning values to successive
/// indices from `0..n`. The `push` method offers special support for this
@@ -293,32 +293,40 @@ impl<'a> From<&'a str> for RefKey<'a> {
}
}
-/// A dictionary entry which tracks key and value span.
+/// A dictionary entry which combines key span and value.
+///
+/// This exists because a key in a directory can't track its span by itself.
#[derive(Clone, PartialEq)]
pub struct SpannedEntry<V> {
- pub key: Span,
- pub val: Spanned<V>,
+ pub key_span: Span,
+ pub value: Spanned<V>,
}
impl<V> SpannedEntry<V> {
/// Create a new entry.
pub fn new(key: Span, val: Spanned<V>) -> Self {
- Self { key, val }
+ Self { key_span: key, value: val }
}
/// Create an entry with the same span for key and value.
pub fn val(val: Spanned<V>) -> Self {
- Self { key: val.span, val }
+ Self { key_span: val.span, value: val }
}
/// Convert from `&SpannedEntry<T>` to `SpannedEntry<&T>`
pub fn as_ref(&self) -> SpannedEntry<&V> {
- SpannedEntry { key: self.key, val: self.val.as_ref() }
+ SpannedEntry {
+ key_span: self.key_span,
+ value: self.value.as_ref(),
+ }
}
/// Map the entry to a different value type.
pub fn map<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
- SpannedEntry { key: self.key, val: self.val.map(f) }
+ SpannedEntry {
+ key_span: self.key_span,
+ value: self.value.map(f),
+ }
}
}
@@ -326,10 +334,10 @@ impl<V: Debug> Debug for SpannedEntry<V> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() {
f.write_str("key")?;
- self.key.fmt(f)?;
+ self.key_span.fmt(f)?;
f.write_str(" ")?;
}
- self.val.fmt(f)
+ self.value.fmt(f)
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 6a63a66f..51bc55ab 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -8,8 +8,8 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
use super::dict::{Dict, SpannedEntry};
use crate::color::RgbaColor;
+use crate::geom::Linear;
use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign};
-use crate::length::{Length, ScaleLength};
use crate::paper::Paper;
use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree};
use crate::{DynFuture, Feedback, Pass};
@@ -21,10 +21,21 @@ pub enum Value {
Ident(Ident),
/// A boolean: `true, false`.
Bool(bool),
- /// A number: `1.2, 200%`.
- Number(f64),
+ /// An integer: `120`.
+ Int(i64),
+ /// A floating-point number: `1.2, 200%`.
+ Float(f64),
/// A length: `2cm, 5.2in`.
- Length(Length),
+ Length(f64),
+ /// A relative value: `50%`.
+ ///
+ /// Note: `50%` is represented as `0.5` here, but as `50.0` in the
+ /// corresponding [literal].
+ ///
+ /// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
+ Relative(f64),
+ /// A combination of an absolute length and a relative value.
+ Linear(Linear),
/// A color value with alpha channel: `#f79143ff`.
Color(RgbaColor),
/// A string: `"string"`.
@@ -40,14 +51,16 @@ pub enum Value {
}
impl Value {
- /// A natural-language name of the type of this expression, e.g.
- /// "identifier".
+ /// The natural-language name of this value for use in error messages.
pub fn name(&self) -> &'static str {
match self {
Self::Ident(_) => "identifier",
Self::Bool(_) => "bool",
- Self::Number(_) => "number",
+ Self::Int(_) => "integer",
+ Self::Float(_) => "float",
+ Self::Relative(_) => "relative",
Self::Length(_) => "length",
+ Self::Linear(_) => "linear",
Self::Color(_) => "color",
Self::Str(_) => "string",
Self::Dict(_) => "dict",
@@ -71,13 +84,13 @@ impl Spanned<Value> {
let mut end = None;
for entry in dict.into_values() {
if let Some(last_end) = end {
- let span = Span::new(last_end, entry.key.start);
+ let span = Span::new(last_end, entry.key_span.start);
let tree = vec![SynNode::Space.span_with(span)];
commands.push(Command::LayoutSyntaxTree(tree));
}
- end = Some(entry.val.span.end);
- commands.extend(entry.val.into_commands());
+ end = Some(entry.value.span.end);
+ commands.extend(entry.value.into_commands());
}
commands
}
@@ -100,11 +113,14 @@ impl Debug for Value {
match self {
Self::Ident(i) => i.fmt(f),
Self::Bool(b) => b.fmt(f),
- Self::Number(n) => n.fmt(f),
- Self::Length(s) => s.fmt(f),
+ Self::Int(i) => i.fmt(f),
+ Self::Float(n) => n.fmt(f),
+ Self::Length(l) => l.fmt(f),
+ Self::Relative(r) => r.fmt(f),
+ Self::Linear(l) => l.fmt(f),
Self::Color(c) => c.fmt(f),
Self::Str(s) => s.fmt(f),
- Self::Dict(t) => t.fmt(f),
+ Self::Dict(d) => d.fmt(f),
Self::Tree(t) => t.fmt(f),
Self::Func(c) => c.fmt(f),
Self::Commands(c) => c.fmt(f),
@@ -117,18 +133,19 @@ impl Debug for Value {
/// The first argument is a dictionary containing the arguments passed to the
/// function. The function may be asynchronous (as such it returns a dynamic
/// future) and it may emit diagnostics, which are contained in the returned
-/// `Pass`. In the end, the function must evaluate to `Value`. Your typical
+/// `Pass`. In the end, the function must evaluate to [`Value`]. A typical
/// typesetting function will return a `Commands` value which will instruct the
/// layouting engine to do what the function pleases.
///
-/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable.
+/// The dynamic function object is wrapped in an `Rc` to keep [`Value`]
+/// clonable.
+///
+/// [`Value`]: enum.Value.html
#[derive(Clone)]
pub struct FuncValue(pub Rc<FuncType>);
-/// The dynamic function type backtick [`FuncValue`].
-///
-/// [`FuncValue`]: struct.FuncValue.html
-pub type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>;
+/// The signature of executable functions.
+type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>;
impl FuncValue {
/// Create a new function value from a rust function or closure.
@@ -175,7 +192,7 @@ impl DictValue {
/// skipping and ignoring all non-matching entries with lower keys.
pub fn take<T: TryFromValue>(&mut self) -> Option<T> {
for (&key, entry) in self.nums() {
- let expr = entry.val.as_ref();
+ let expr = entry.value.as_ref();
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
self.remove(key);
return Some(val);
@@ -197,7 +214,7 @@ impl DictValue {
) -> Option<T> {
while let Some((num, _)) = self.first() {
let entry = self.remove(num).unwrap();
- if let Some(val) = T::try_from_value(entry.val.as_ref(), f) {
+ if let Some(val) = T::try_from_value(entry.value.as_ref(), f) {
return Some(val);
}
}
@@ -214,7 +231,7 @@ impl DictValue {
T: TryFromValue,
{
self.remove(key).and_then(|entry| {
- let expr = entry.val.as_ref();
+ let expr = entry.value.as_ref();
T::try_from_value(expr, f)
})
}
@@ -230,7 +247,7 @@ impl DictValue {
let mut skip = 0;
std::iter::from_fn(move || {
for (&key, entry) in self.nums().skip(skip) {
- let expr = entry.val.as_ref();
+ let expr = entry.value.as_ref();
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
self.remove(key);
return Some((key, val));
@@ -265,7 +282,7 @@ impl DictValue {
let mut skip = 0;
std::iter::from_fn(move || {
for (key, entry) in self.strs().skip(skip) {
- let expr = entry.val.as_ref();
+ let expr = entry.value.as_ref();
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
let key = key.clone();
self.remove(&key);
@@ -281,7 +298,7 @@ impl DictValue {
/// Generated `"unexpected argument"` errors for all remaining entries.
pub fn unexpected(&self, f: &mut Feedback) {
for entry in self.values() {
- error!(@f, entry.key.join(entry.val.span), "unexpected argument");
+ error!(@f, entry.key_span.join(entry.value.span), "unexpected argument");
}
}
}
@@ -351,20 +368,50 @@ impl<T: TryFromValue> TryFromValue for Spanned<T> {
impl_match!(Value, "value", v => v.clone());
impl_match!(Ident, "identifier", Value::Ident(i) => i.clone());
-impl_match!(String, "string", Value::Str(s) => s.clone());
impl_match!(bool, "bool", &Value::Bool(b) => b);
-impl_match!(f64, "number", &Value::Number(n) => n);
-impl_match!(Length, "length", &Value::Length(l) => l);
+impl_match!(i64, "integer", &Value::Int(i) => i);
+impl_match!(f64, "float",
+ &Value::Int(i) => i as f64,
+ &Value::Float(f) => f,
+);
+impl_match!(Abs, "length", &Value::Length(l) => Abs(l));
+impl_match!(Rel, "relative", &Value::Relative(r) => Rel(r));
+impl_match!(Linear, "linear",
+ &Value::Linear(l) => l,
+ &Value::Length(l) => Linear::abs(l),
+ &Value::Relative(r) => Linear::rel(r),
+);
+impl_match!(String, "string", Value::Str(s) => s.clone());
impl_match!(SynTree, "tree", Value::Tree(t) => t.clone());
impl_match!(DictValue, "dict", Value::Dict(t) => t.clone());
impl_match!(FuncValue, "function", Value::Func(f) => f.clone());
-impl_match!(ScaleLength, "number or length",
- &Value::Length(length) => ScaleLength::Absolute(length),
- &Value::Number(scale) => ScaleLength::Scaled(scale),
-);
-/// A value type that matches identifiers and strings and implements
-/// `Into<String>`.
+/// A value type that matches [length] values.
+///
+/// [length]: enum.Value.html#variant.Length
+pub struct Abs(pub f64);
+
+impl From<Abs> for f64 {
+ fn from(abs: Abs) -> f64 {
+ abs.0
+ }
+}
+
+/// A value type that matches [relative] values.
+///
+/// [relative]: enum.Value.html#variant.Relative
+pub struct Rel(pub f64);
+
+impl From<Rel> for f64 {
+ fn from(rel: Rel) -> f64 {
+ rel.0
+ }
+}
+
+/// A value type that matches [identifier] and [string] values.
+///
+/// [identifier]: enum.Value.html#variant.Ident
+/// [string]: enum.Value.html#variant.Str
pub struct StringLike(pub String);
impl From<StringLike> for String {
@@ -410,19 +457,19 @@ impl_ident!(Paper, "paper", Self::from_name);
impl TryFromValue for FontWeight {
fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
match value.v {
- &Value::Number(weight) => {
- const MIN: u16 = 100;
- const MAX: u16 = 900;
-
- if weight < MIN as f64 {
+ &Value::Int(weight) => {
+ const MIN: i64 = 100;
+ const MAX: i64 = 900;
+ let weight = if weight < MIN {
error!(@f, value.span, "the minimum font weight is {}", MIN);
- Some(Self::THIN)
- } else if weight > MAX as f64 {
+ MIN
+ } else if weight > MAX {
error!(@f, value.span, "the maximum font weight is {}", MAX);
- Some(Self::BLACK)
+ MAX
} else {
- FontWeight::from_number(weight.round() as u16)
- }
+ weight
+ };
+ Self::from_number(weight as u16)
}
Value::Ident(ident) => {
let weight = Self::from_str(ident);
@@ -434,7 +481,7 @@ impl TryFromValue for FontWeight {
other => {
error!(
@f, value.span,
- "expected font weight (name or number), found {}",
+ "expected font weight (name or integer), found {}",
other.name(),
);
None
@@ -490,7 +537,7 @@ mod tests {
assert_eq!(dict.take_key::<f64>("hi", &mut f), None);
assert_eq!(f.diagnostics, [error!(
Span::ZERO,
- "expected number, found bool"
+ "expected float, found bool"
)]);
assert!(dict.is_empty());
}
@@ -499,13 +546,13 @@ mod tests {
fn test_dict_take_all_removes_the_correct_entries() {
let mut dict = Dict::new();
dict.insert(1, entry(Value::Bool(false)));
- dict.insert(3, entry(Value::Number(0.0)));
+ dict.insert(3, entry(Value::Float(0.0)));
dict.insert(7, entry(Value::Bool(true)));
assert_eq!(dict.take_all_num::<bool>().collect::<Vec<_>>(), [
(1, false),
(7, true)
],);
assert_eq!(dict.len(), 1);
- assert_eq!(dict[3].val.v, Value::Number(0.0));
+ assert_eq!(dict[3].value.v, Value::Float(0.0));
}
}