diff --git a/src/eval/dict.rs b/src/eval/dict.rs index e62165724..7f4426ff3 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 { - pub key: Span, - pub val: Spanned, + pub key_span: Span, + pub value: Spanned, } impl SpannedEntry { /// Create a new entry. pub fn new(key: Span, val: Spanned) -> 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) -> Self { - Self { key: val.span, val } + Self { key_span: val.span, value: val } } /// Convert from `&SpannedEntry` 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(self, f: impl FnOnce(V) -> U) -> SpannedEntry { - 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 Debug for SpannedEntry { 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 6a63a66f7..51bc55ab0 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 { 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); -/// The dynamic function type backtick [`FuncValue`]. -/// -/// [`FuncValue`]: struct.FuncValue.html -pub type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture>; +/// The signature of executable functions. +type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture>; 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(&mut self) -> Option { 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 { 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 TryFromValue for Spanned { 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`. +/// A value type that matches [length] values. +/// +/// [length]: enum.Value.html#variant.Length +pub struct Abs(pub f64); + +impl From 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 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 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 { 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::("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::().collect::>(), [ (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)); } } diff --git a/src/geom.rs b/src/geom.rs index 6aa875f14..05143f22a 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -3,6 +3,9 @@ #[doc(no_inline)] pub use kurbo::*; +use std::fmt::{self, Debug, Formatter}; +use std::ops::*; + use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis}; /// Additional methods for [sizes]. @@ -176,3 +179,128 @@ impl Sides { } } } + +/// A function that depends linearly on one value. +/// +/// This represents a function `f(x) = rel * x + abs`. +#[derive(Copy, Clone, PartialEq)] +pub struct Linear { + /// The relative part. + pub rel: f64, + /// The absolute part. + pub abs: f64, +} + +impl Linear { + /// The constant zero function. + pub const ZERO: Linear = Linear { rel: 0.0, abs: 0.0 }; + + /// Create a new linear function. + pub fn new(rel: f64, abs: f64) -> Self { + Self { rel, abs } + } + + /// Create a new linear function with only a relative component. + pub fn rel(rel: f64) -> Self { + Self { rel, abs: 0.0 } + } + + /// Create a new linear function with only an absolute component. + pub fn abs(abs: f64) -> Self { + Self { rel: 0.0, abs } + } + + /// Evaluate the linear function with the given value. + pub fn eval(self, x: f64) -> f64 { + self.rel * x + self.abs + } +} + +impl Add for Linear { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + rel: self.rel + other.rel, + abs: self.abs + other.abs, + } + } +} + +impl AddAssign for Linear { + fn add_assign(&mut self, other: Self) { + self.rel += other.rel; + self.abs += other.abs; + } +} + +impl Sub for Linear { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + rel: self.rel - other.rel, + abs: self.abs - other.abs, + } + } +} + +impl SubAssign for Linear { + fn sub_assign(&mut self, other: Self) { + self.rel -= other.rel; + self.abs -= other.abs; + } +} + +impl Mul for Linear { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { + rel: self.rel + other, + abs: self.abs + other, + } + } +} + +impl MulAssign for Linear { + fn mul_assign(&mut self, other: f64) { + self.rel *= other; + self.abs *= other; + } +} + +impl Mul for f64 { + type Output = Linear; + + fn mul(self, other: Linear) -> Linear { + Linear { + rel: self + other.rel, + abs: self + other.abs, + } + } +} + +impl Div for Linear { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { + rel: self.rel / other, + abs: self.abs / other, + } + } +} + +impl DivAssign for Linear { + fn div_assign(&mut self, other: f64) { + self.rel /= other; + self.abs /= other; + } +} + +impl Debug for Linear { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}x + {}", self.rel, self.abs) + } +} diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 587742154..18156e972 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -115,7 +115,9 @@ impl<'a> TreeLayouter<'a> { async fn layout_heading(&mut self, heading: &NodeHeading) { let style = self.style.text.clone(); - self.style.text.font_scale *= 1.5 - 0.1 * heading.level.v as f64; + + let factor = 1.5 - 0.1 * heading.level.v as f64; + self.style.text.font_size.scale *= factor; self.style.text.strong = true; self.layout_parbreak(); diff --git a/src/length.rs b/src/length.rs index fb62763e6..437c741df 100644 --- a/src/length.rs +++ b/src/length.rs @@ -177,38 +177,6 @@ impl Display for ParseLengthError { } } -/// Either an absolute length or a factor of some entity. -#[derive(Copy, Clone, PartialEq)] -pub enum ScaleLength { - Absolute(Length), - Scaled(f64), -} - -impl ScaleLength { - /// Use the absolute value or scale the entity. - pub fn raw_scaled(&self, entity: f64) -> f64 { - match *self { - ScaleLength::Absolute(l) => l.as_raw(), - ScaleLength::Scaled(s) => s * entity, - } - } -} - -impl Display for ScaleLength { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ScaleLength::Absolute(length) => write!(f, "{}", length), - ScaleLength::Scaled(scale) => write!(f, "{}%", scale * 100.0), - } - } -} - -impl Debug for ScaleLength { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/library/boxed.rs b/src/library/boxed.rs index ac0bc19ec..b36a151b9 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,5 +1,5 @@ use super::*; -use crate::length::ScaleLength; +use crate::geom::Linear; /// `box`: Layouts its contents into a box. /// @@ -19,17 +19,17 @@ pub async fn boxed( ctx.spaces.truncate(1); ctx.repeat = false; - if let Some(width) = args.take_key::("width", &mut f) { - let length = width.raw_scaled(ctx.base.width); - ctx.base.width = length; - ctx.spaces[0].size.width = length; + if let Some(width) = args.take_key::("width", &mut f) { + let abs = width.eval(ctx.base.width); + ctx.base.width = abs; + ctx.spaces[0].size.width = abs; ctx.spaces[0].expansion.horizontal = true; } - if let Some(height) = args.take_key::("height", &mut f) { - let length = height.raw_scaled(ctx.base.height); - ctx.base.height = length; - ctx.spaces[0].size.height = length; + if let Some(height) = args.take_key::("height", &mut f) { + let abs = height.eval(ctx.base.height); + ctx.base.height = abs; + ctx.spaces[0].size.height = abs; ctx.spaces[0].expansion.vertical = true; } diff --git a/src/library/color.rs b/src/library/color.rs index 43d9253fb..a11966ea4 100644 --- a/src/library/color.rs +++ b/src/library/color.rs @@ -5,20 +5,18 @@ use crate::color::RgbaColor; pub async fn rgb(span: Span, mut args: DictValue, _: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); - let r = args.expect::>("red value", span, &mut f); - let g = args.expect::>("green value", span, &mut f); - let b = args.expect::>("blue value", span, &mut f); - let a = args.take::>(); + let r = args.expect::>("red value", span, &mut f); + let g = args.expect::>("green value", span, &mut f); + let b = args.expect::>("blue value", span, &mut f); + let a = args.take::>(); - let mut clamp = |component: Option>, default| { - component - .map(|c| { - if c.v < 0.0 || c.v > 255.0 { - error!(@f, c.span, "should be between 0 and 255") - } - c.v.min(255.0).max(0.0).round() as u8 - }) - .unwrap_or(default) + let mut clamp = |component: Option>, default| { + component.map_or(default, |c| { + if c.v < 0 || c.v > 255 { + error!(@f, c.span, "should be between 0 and 255") + } + c.v.max(0).min(255) as u8 + }) }; let color = RgbaColor::new(clamp(r, 0), clamp(g, 0), clamp(b, 0), clamp(a, 255)); diff --git a/src/library/font.rs b/src/library/font.rs index 1d58c4cea..e12bda2f1 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -2,7 +2,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight}; use super::*; use crate::eval::StringLike; -use crate::length::ScaleLength; +use crate::geom::Linear; /// `font`: Configure the font. /// @@ -56,13 +56,12 @@ pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< let content = args.take::(); - if let Some(s) = args.take::() { - match s { - ScaleLength::Absolute(length) => { - text.base_font_size = length.as_raw(); - text.font_scale = 1.0; - } - ScaleLength::Scaled(scale) => text.font_scale = scale, + if let Some(linear) = args.take::() { + if linear.rel == 0.0 { + text.font_size.base = linear.abs; + text.font_size.scale = Linear::rel(1.0); + } else { + text.font_size.scale = linear; } } diff --git a/src/library/page.rs b/src/library/page.rs index 8188bb672..77eb6244f 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,8 +1,8 @@ use std::mem; use super::*; -use crate::geom::Sides; -use crate::length::{Length, ScaleLength}; +use crate::eval::Abs; +use crate::geom::{Linear, Sides}; use crate::paper::{Paper, PaperClass}; /// `page`: Configure pages. @@ -28,33 +28,33 @@ pub async fn page(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< style.size = paper.size(); } - if let Some(width) = args.take_key::("width", &mut f) { + if let Some(Abs(width)) = args.take_key::("width", &mut f) { style.class = PaperClass::Custom; - style.size.width = width.as_raw(); + style.size.width = width; } - if let Some(height) = args.take_key::("height", &mut f) { + if let Some(Abs(height)) = args.take_key::("height", &mut f) { style.class = PaperClass::Custom; - style.size.height = height.as_raw(); + style.size.height = height; } - if let Some(margins) = args.take_key::("margins", &mut f) { + if let Some(margins) = args.take_key::("margins", &mut f) { style.margins = Sides::uniform(Some(margins)); } - if let Some(left) = args.take_key::("left", &mut f) { + if let Some(left) = args.take_key::("left", &mut f) { style.margins.left = Some(left); } - if let Some(top) = args.take_key::("top", &mut f) { + if let Some(top) = args.take_key::("top", &mut f) { style.margins.top = Some(top); } - if let Some(right) = args.take_key::("right", &mut f) { + if let Some(right) = args.take_key::("right", &mut f) { style.margins.right = Some(right); } - if let Some(bottom) = args.take_key::("bottom", &mut f) { + if let Some(bottom) = args.take_key::("bottom", &mut f) { style.margins.bottom = Some(bottom); } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6596717bd..03f52ba4d 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -1,6 +1,6 @@ use super::*; +use crate::geom::Linear; use crate::layout::SpacingKind; -use crate::length::ScaleLength; /// `h`: Add horizontal spacing. /// @@ -26,10 +26,10 @@ fn spacing( ) -> Pass { let mut f = Feedback::new(); - let spacing = args.expect::("spacing", name, &mut f); + let spacing = args.expect::("spacing", name, &mut f); let commands = if let Some(spacing) = spacing { let axis = axis.to_gen(ctx.sys); - let spacing = spacing.raw_scaled(ctx.style.text.font_size()); + let spacing = spacing.eval(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] diff --git a/src/paper.rs b/src/paper.rs index 8f855d5a1..30e5d8e7f 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -1,7 +1,7 @@ //! Predefined papers. -use crate::geom::{Sides, Size}; -use crate::length::{Length, ScaleLength}; +use crate::geom::{Linear, Sides, Size}; +use crate::length::Length; /// Specification of a paper. #[derive(Debug, Copy, Clone, PartialEq)] @@ -38,15 +38,15 @@ pub enum PaperClass { impl PaperClass { /// The default margin ratios for this page class. - pub fn default_margins(self) -> Sides { - let s = ScaleLength::Scaled; - let f = |l, r, t, b| Sides::new(s(l), s(r), s(t), s(b)); + pub fn default_margins(self) -> Sides { + let f = Linear::rel; + let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b)); match self { - Self::Custom => f(0.1190, 0.0842, 0.1190, 0.0842), - Self::Base => f(0.1190, 0.0842, 0.1190, 0.0842), - Self::US => f(0.1760, 0.1092, 0.1760, 0.0910), - Self::Newspaper => f(0.0455, 0.0587, 0.0455, 0.0294), - Self::Book => f(0.1200, 0.0852, 0.1500, 0.0965), + Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842), + Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842), + Self::US => s(0.1760, 0.1092, 0.1760, 0.0910), + Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294), + Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965), } } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 42102bf54..916f2fdc3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -72,6 +72,7 @@ fn node(p: &mut Parser, at_start: bool) -> Option> { SynNode::Text(p.eaten_from(start).into()) } } + Token::NonBreakingSpace => SynNode::Text("\u{00A0}".into()), Token::Raw(token) => SynNode::Raw(raw(p, token)), Token::UnicodeEscape(token) => SynNode::Text(unicode_escape(p, token, start)), @@ -425,8 +426,10 @@ fn value(p: &mut Parser) -> Option { // Atomic values. Token::Bool(b) => Expr::Lit(Lit::Bool(b)), - Token::Number(f) => Expr::Lit(Lit::Float(f)), + Token::Int(i) => Expr::Lit(Lit::Int(i)), + Token::Float(f) => Expr::Lit(Lit::Float(f)), Token::Length(l) => Expr::Lit(Lit::Length(l)), + Token::Percent(p) => Expr::Lit(Lit::Percent(p)), Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))), Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))), diff --git a/src/parse/tests.rs b/src/parse/tests.rs index feff2b9ab..4315fd2c6 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -57,13 +57,13 @@ fn Id(ident: &str) -> Expr { fn Bool(b: bool) -> Expr { Expr::Lit(Lit::Bool(b)) } -fn _Int(int: i64) -> Expr { +fn Int(int: i64) -> Expr { Expr::Lit(Lit::Int(int)) } fn Float(float: f64) -> Expr { Expr::Lit(Lit::Float(float)) } -fn _Percent(percent: f64) -> Expr { +fn Percent(percent: f64) -> Expr { Expr::Lit(Lit::Percent(percent)) } fn Len(length: Length) -> Expr { @@ -334,7 +334,7 @@ fn test_parse_function_names() { t!("[ f]" => F!("f")); // An invalid name. - e!("[12]" => s(1, 3, "expected function name, found number")); + e!("[12]" => s(1, 3, "expected function name, found integer")); e!("[ 🌎]" => s(3, 7, "expected function name, found invalid token")); } @@ -345,7 +345,7 @@ fn test_parse_chaining() { t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![ F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi"))) ])); - t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Float(400.0), Tree![ + t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![ F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0))))) ])); @@ -374,7 +374,7 @@ fn test_parse_colon_starting_func_args() { #[test] fn test_parse_function_bodies() { - t!("[val: 1][*Hi*]" => F!("val"; Float(1.0), Tree![B, T("Hi"), B])); + t!("[val: 1][*Hi*]" => F!("val"; Int(1), Tree![B, T("Hi"), B])); e!(" [val][ */]" => s(8, 10, "unexpected end of block comment")); // Raw in body. @@ -406,7 +406,7 @@ fn test_parse_values() { v!("false" => Bool(false)); v!("1.0e-4" => Float(1e-4)); v!("3.14" => Float(3.14)); - v!("50%" => Float(0.5)); + v!("50%" => Percent(50.0)); v!("4.5cm" => Len(Length::cm(4.5))); v!("12e1pt" => Len(Length::pt(12e1))); v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00))); @@ -440,43 +440,43 @@ fn test_parse_expressions() { v!("(hi)" => Id("hi")); // Operations. - v!("-1" => Unary(Neg, Float(1.0))); - v!("-- 1" => Unary(Neg, Unary(Neg, Float(1.0)))); + v!("-1" => Unary(Neg, Int(1))); + v!("-- 1" => Unary(Neg, Unary(Neg, Int(1)))); v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0)))); - v!("5 - 0.01" => Binary(Sub, Float(5.0), Float(0.01))); - v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Float(2.0))); + v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01))); + v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2))); v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0)))); // More complex. v!("(3.2in + 6pt)*(5/2-1)" => Binary( Mul, Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))), - Binary(Sub, Binary(Div, Float(5.0), Float(2.0)), Float(1.0)) + Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1)) )); v!("(6.3E+2+4* - 3.2pt)/2" => Binary( Div, Binary(Add, Float(6.3e2), Binary( Mul, - Float(4.0), + Int(4), Unary(Neg, Len(Length::pt(3.2))) )), - Float(2.0) + Int(2) )); // Associativity of multiplication and division. - v!("3/4*5" => Binary(Mul, Binary(Div, Float(3.0), Float(4.0)), Float(5.0))); + v!("3/4*5" => Binary(Mul, Binary(Div, Int(3), Int(4)), Int(5))); // Spanned. ts!("[val: 1 + 3]" => s(0, 12, F!( s(1, 4, "val"); s(6, 11, Binary( s(8, 9, Add), - s(6, 7, Float(1.0)), - s(10, 11, Float(3.0)) + s(6, 7, Int(1)), + s(10, 11, Int(3)) )) ))); // Span of parenthesized expression contains parens. - ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.0))))); + ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Int(1))))); // Invalid expressions. v!("4pt--" => Len(Length::pt(4.0))); @@ -494,8 +494,8 @@ fn test_parse_dicts() { v!("(false)" => Bool(false)); v!("(true,)" => Dict![Bool(true)]); v!("(key=val)" => Dict!["key" => Id("val")]); - v!("(1, 2)" => Dict![Float(1.0), Float(2.0)]); - v!("(1, key=\"value\")" => Dict![Float(1.0), "key" => Str("value")]); + v!("(1, 2)" => Dict![Int(1), Int(2)]); + v!("(1, key=\"value\")" => Dict![Int(1), "key" => Str("value")]); // Decorations. d!("[val: key=hi]" => s(6, 9, DictKey)); @@ -513,7 +513,7 @@ fn test_parse_dicts() { #[test] fn test_parse_dicts_compute_func_calls() { v!("empty()" => Call!("empty")); - v!("add ( 1 , 2 )" => Call!("add"; Float(1.0), Float(2.0))); + v!("add ( 1 , 2 )" => Call!("add"; Int(1), Int(2))); v!("items(\"fire\", #f93a6d)" => Call!("items"; Str("fire"), Color(RgbaColor::new(0xf9, 0x3a, 0x6d, 0xff)) )); @@ -522,7 +522,7 @@ fn test_parse_dicts_compute_func_calls() { v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!( "css"; Len(Length::pt(1.0)), - Call!("rgb"; Float(90.0), Float(102.0), Float(254.0)), + Call!("rgb"; Int(90), Int(102), Int(254)), Str("solid"), )); @@ -539,10 +539,10 @@ fn test_parse_dicts_compute_func_calls() { fn test_parse_dicts_nested() { v!("(1, ( ab=(), d = (3, 14pt) )), false" => Dict![ - Float(1.0), + Int(1), Dict!( "ab" => Dict![], - "d" => Dict!(Float(3.0), Len(Length::pt(14.0))), + "d" => Dict!(Int(3), Len(Length::pt(14.0))), ), ], Bool(false), @@ -576,7 +576,7 @@ fn test_parse_dicts_errors() { s(10, 11, "expected value, found equals sign")); // Unexpected equals sign. - v!("z=y=4" => "z" => Id("y"), Float(4.0)); + v!("z=y=4" => "z" => Id("y"), Int(4)); e!("[val: z=y=4]" => s(9, 9, "expected comma"), s(9, 10, "expected value, found equals sign")); diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 77e7e92ed..0f6fb3781 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -87,8 +87,8 @@ impl<'s> Iterator for Tokens<'s> { '*' if self.mode == Body => Token::Star, '_' if self.mode == Body => Token::Underscore, '#' if self.mode == Body => Token::Hashtag, + '~' if self.mode == Body => Token::NonBreakingSpace, '`' if self.mode == Body => self.read_raw(), - '~' if self.mode == Body => Token::Text("\u{00A0}"), '\\' if self.mode == Body => self.read_escaped(), // Syntactic elements in headers. @@ -273,10 +273,12 @@ impl Debug for Tokens<'_> { fn parse_expr(text: &str) -> Token<'_> { if let Ok(b) = text.parse::() { Token::Bool(b) + } else if let Ok(int) = text.parse::() { + Token::Int(int) } else if let Ok(num) = text.parse::() { - Token::Number(num) - } else if let Some(num) = parse_percent(text) { - Token::Number(num / 100.0) + Token::Float(num) + } else if let Some(percent) = parse_percent(text) { + Token::Percent(percent) } else if let Ok(length) = text.parse::() { Token::Length(length) } else if is_ident(text) { @@ -298,10 +300,10 @@ mod tests { use crate::parse::tests::check; use Token::{ - BlockComment as BC, Bool, Chain, Hex, Hyphen as Min, Ident as Id, + BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int, LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len, - LineComment as LC, Number as Num, Plus, RightBrace as RB, RightBracket as R, - RightParen as RP, Slash, Space as S, Star, Text as T, *, + LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB, + RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *, }; fn Str(string: &str, terminated: bool) -> Token { @@ -337,7 +339,7 @@ mod tests { t!(Body, " \n\t \n " => S(2)); t!(Body, "\n\r" => S(2)); t!(Body, " \r\r\n \x0D" => S(3)); - t!(Body, "a~b" => T("a"), T("\u{00A0}"), T("b")); + t!(Body, "a~b" => T("a"), Nbsp, T("b")); } #[test] @@ -424,24 +426,24 @@ mod tests { t!(Header, ">main" => Invalid(">main")); t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma); t!(Header, "{abc}" => LB, Id("abc"), RB); - t!(Header, "(1,2)" => LP, Num(1.0), Comma, Num(2.0), RP); + t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP); t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0))); t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g")); - t!(Header, "=3.14" => Equals, Num(3.14)); + t!(Header, "=3.14" => Equals, Float(3.14)); t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")); t!(Header, "a:b" => Id("a"), Colon, Id("b")); t!(Header, "(){}:=," => LP, RP, LB, RB, Colon, Equals, Comma); t!(Body, "c=d, " => T("c=d,"), S(0)); t!(Body, "a: b" => T("a:"), S(0), T("b")); t!(Header, "a: true, x=1" => Id("a"), Colon, S(0), Bool(true), Comma, S(0), - Id("x"), Equals, Num(1.0)); + Id("x"), Equals, Int(1)); } #[test] fn tokenize_numeric_values() { - t!(Header, "12.3e5" => Num(12.3e5)); - t!(Header, "120%" => Num(1.2)); - t!(Header, "12e4%" => Num(1200.0)); + t!(Header, "12.3e5" => Float(12.3e5)); + t!(Header, "120%" => Percent(120.0)); + t!(Header, "12e4%" => Percent(120000.0)); t!(Header, "1e5in" => Len(Length::inches(100000.0))); t!(Header, "2.3cm" => Len(Length::cm(2.3))); t!(Header, "02.4mm" => Len(Length::mm(2.4))); @@ -456,7 +458,7 @@ mod tests { t!(Header, "\"hello" => Str("hello", false)); t!(Header, "\"hello world\"" => Str("hello world", true)); t!(Header, "\"hello\nworld\"" => Str("hello\nworld", true)); - t!(Header, r#"1"hello\nworld"false"# => Num(1.0), Str("hello\\nworld", true), Bool(false)); + t!(Header, r#"1"hello\nworld"false"# => Int(1), Str("hello\\nworld", true), Bool(false)); t!(Header, r#""a\"bc""# => Str(r#"a\"bc"#, true)); t!(Header, r#""a\\"bc""# => Str(r#"a\\"#, true), Id("bc"), Str("", false)); t!(Header, r#""a\tbc"# => Str("a\\tbc", false)); @@ -466,12 +468,12 @@ mod tests { #[test] fn tokenize_math() { t!(Header, "12e-3in" => Len(Length::inches(12e-3))); - t!(Header, "-1" => Min, Num(1.0)); - t!(Header, "--1" => Min, Min, Num(1.0)); - t!(Header, "- 1" => Min, S(0), Num(1.0)); + t!(Header, "-1" => Min, Int(1)); + t!(Header, "--1" => Min, Min, Int(1)); + t!(Header, "- 1" => Min, S(0), Int(1)); t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)), - Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0)); - t!(Header, "(5 - 1) / 2.1" => LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP, - S(0), Slash, S(0), Num(2.1)); + Comma, Id("a"), Equals, Int(1), Star, Int(2)); + t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP, + S(0), Slash, S(0), Float(2.1)); } } diff --git a/src/style.rs b/src/style.rs index b6c1a2782..74deeeb95 100644 --- a/src/style.rs +++ b/src/style.rs @@ -2,8 +2,8 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; -use crate::geom::{Insets, Sides, Size}; -use crate::length::{Length, ScaleLength}; +use crate::geom::{Insets, Linear, Sides, Size}; +use crate::length::Length; use crate::paper::{Paper, PaperClass, PAPER_A4}; /// Defines properties of pages and text. @@ -28,37 +28,35 @@ pub struct TextStyle { /// Whether the emphasis toggle is active or inactive. This determines /// whether the next `_` makes italic or non-italic. pub emph: bool, - /// The base font size. - pub base_font_size: f64, - /// The font scale to apply on the base font size. - pub font_scale: f64, - /// The word spacing (as a multiple of the font size). - pub word_spacing_scale: f64, - /// The line spacing (as a multiple of the font size). - pub line_spacing_scale: f64, - /// The paragraphs spacing (as a multiple of the font size). - pub paragraph_spacing_scale: f64, + /// The font size. + pub font_size: FontSize, + /// The word spacing (relative to the the font size). + pub word_spacing: Linear, + /// The line spacing (relative to the the font size). + pub line_spacing: Linear, + /// The paragraphs spacing (relative to the the font size). + pub par_spacing: Linear, } impl TextStyle { - /// The scaled font size. + /// The absolute font size. pub fn font_size(&self) -> f64 { - self.base_font_size * self.font_scale + self.font_size.eval() } /// The absolute word spacing. pub fn word_spacing(&self) -> f64 { - self.word_spacing_scale * self.font_size() + self.word_spacing.eval(self.font_size()) } /// The absolute line spacing. pub fn line_spacing(&self) -> f64 { - (self.line_spacing_scale - 1.0) * self.font_size() + self.line_spacing.eval(self.font_size()) } /// The absolute paragraph spacing. pub fn paragraph_spacing(&self) -> f64 { - (self.paragraph_spacing_scale - 1.0) * self.font_size() + self.par_spacing.eval(self.font_size()) } } @@ -85,15 +83,41 @@ impl Default for TextStyle { }, strong: false, emph: false, - base_font_size: Length::pt(11.0).as_raw(), - font_scale: 1.0, - word_spacing_scale: 0.25, - line_spacing_scale: 1.2, - paragraph_spacing_scale: 1.5, + font_size: FontSize::abs(Length::pt(11.0).as_raw()), + word_spacing: Linear::rel(0.25), + line_spacing: Linear::rel(0.2), + par_spacing: Linear::rel(0.5), } } } +/// The font size, defined by base and scale. +#[derive(Debug, Clone, PartialEq)] +pub struct FontSize { + /// The base font size, updated whenever the font size is set absolutely. + pub base: f64, + /// The scale to apply on the base font size, updated when the font size + /// is set relatively. + pub scale: Linear, +} + +impl FontSize { + /// Create a new font size. + pub fn new(base: f64, scale: Linear) -> Self { + Self { base, scale } + } + + /// Create a new font size with the given `base` and a scale of `1.0`. + pub fn abs(base: f64) -> Self { + Self::new(base, Linear::rel(1.0)) + } + + /// Compute the absolute font size. + pub fn eval(&self) -> f64 { + self.scale.eval(self.base) + } +} + /// Defines the size and margins of a page. #[derive(Debug, Copy, Clone, PartialEq)] pub struct PageStyle { @@ -103,7 +127,7 @@ pub struct PageStyle { pub size: Size, /// The amount of white space in the order [left, top, right, bottom]. If a /// side is set to `None`, the default for the paper class is used. - pub margins: Sides>, + pub margins: Sides>, } impl PageStyle { @@ -121,10 +145,10 @@ impl PageStyle { let Size { width, height } = self.size; let default = self.class.default_margins(); Insets { - x0: -self.margins.left.unwrap_or(default.left).raw_scaled(width), - y0: -self.margins.top.unwrap_or(default.top).raw_scaled(height), - x1: -self.margins.right.unwrap_or(default.right).raw_scaled(width), - y1: -self.margins.bottom.unwrap_or(default.bottom).raw_scaled(height), + x0: -self.margins.left.unwrap_or(default.left).eval(width), + y0: -self.margins.top.unwrap_or(default.top).eval(height), + x1: -self.margins.right.unwrap_or(default.right).eval(width), + y1: -self.margins.bottom.unwrap_or(default.bottom).eval(height), } } } diff --git a/src/syntax/ast/lit.rs b/src/syntax/ast/lit.rs index bbdd0c81a..ba7e7e4fe 100644 --- a/src/syntax/ast/lit.rs +++ b/src/syntax/ast/lit.rs @@ -18,10 +18,15 @@ pub enum Lit { Int(i64), /// A floating-point literal: `1.2`, `10e-4`. Float(f64), - /// A percent literal: `50%`. - Percent(f64), /// A length literal: `12pt`, `3cm`. Length(Length), + /// A percent literal: `50%`. + /// + /// Note: `50%` is represented as `50.0` here, but as `0.5` in the + /// corresponding [value]. + /// + /// [value]: ../../eval/enum.Value.html#variant.Relative + Percent(f64), /// A color literal: `#ffccee`. Color(RgbaColor), /// A string literal: `"hello!"`. @@ -42,10 +47,10 @@ impl Lit { match *self { Lit::Ident(ref i) => Value::Ident(i.clone()), Lit::Bool(b) => Value::Bool(b), - Lit::Int(i) => Value::Number(i as f64), - Lit::Float(f) => Value::Number(f as f64), - Lit::Percent(p) => Value::Number(p as f64 / 100.0), - Lit::Length(l) => Value::Length(l), + Lit::Int(i) => Value::Int(i), + Lit::Float(f) => Value::Float(f), + Lit::Length(l) => Value::Length(l.as_raw()), + Lit::Percent(p) => Value::Relative(p / 100.0), Lit::Color(c) => Value::Color(c), Lit::Str(ref s) => Value::Str(s.clone()), Lit::Dict(ref d) => Value::Dict(d.eval(ctx, f).await), diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 5c159bbd8..e3fada434 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -5,79 +5,91 @@ use crate::length::Length; /// A minimal semantic entity of source code. #[derive(Debug, Copy, Clone, PartialEq)] pub enum Token<'s> { - /// One or more whitespace characters. The contained `usize` denotes the - /// number of newlines that were contained in the whitespace. + /// One or more whitespace characters. + /// + /// The contained `usize` denotes the number of newlines that were contained + /// in the whitespace. Space(usize), /// A consecutive non-markup string. Text(&'s str), /// A line comment with inner string contents `//\n`. LineComment(&'s str), - /// A block comment with inner string contents `/**/`. The comment - /// can contain nested block comments. + /// A block comment with inner string contents `/**/`. + /// + /// The comment can contain nested block comments. BlockComment(&'s str), - /// A star. It can appear in a function header where it signifies the - /// multiplication of expressions or the body where it modifies the styling. + /// A star: `*`. Star, - /// An underscore in body-text. + /// An underscore: `_`. Underscore, - /// A backslash followed by whitespace in text. + /// A backslash followed by whitespace: `\`. Backslash, - /// A hashtag indicating a section heading. + /// A hashtag indicating a section heading: `#`. Hashtag, - /// A raw block. + /// A non-breaking space: `~`. + NonBreakingSpace, + /// A raw block: `` `...` ``. Raw(TokenRaw<'s>), - /// A unicode escape sequence. + /// A unicode escape sequence: `\u{1F5FA}`. UnicodeEscape(TokenUnicodeEscape<'s>), - /// A left bracket starting a function invocation or body: `[`. + /// A left bracket: `[`. LeftBracket, - /// A right bracket ending a function invocation or body: `]`. + /// A right bracket: `]`. RightBracket, - /// A left brace indicating the start of content: `{`. + /// A left brace: `{`. LeftBrace, - /// A right brace indicating the end of content: `}`. + /// A right brace: `}`. RightBrace, - /// A left parenthesis in a function header: `(`. + /// A left parenthesis: `(`. LeftParen, - /// A right parenthesis in a function header: `)`. + /// A right parenthesis: `)`. RightParen, - /// A colon in a function header: `:`. + /// A colon: `:`. Colon, - /// A comma in a function header: `,`. + /// A comma: `,`. Comma, - /// An equals sign in a function header: `=`. + /// An equals sign: `=`. Equals, - /// A double forward chevron in a function header: `>>`. + /// A double forward chevron: `>>`. Chain, - /// A plus in a function header, signifying the addition of expressions. + /// A plus: `+`. Plus, - /// A hyphen in a function header, signifying the subtraction of - /// expressions. + /// A hyphen: `-`. Hyphen, - /// A slash in a function header, signifying the division of expressions. + /// A slash: `/`. Slash, - /// An identifier in a function header: `center`. + /// An identifier: `center`. Ident(&'s str), - /// A boolean in a function header: `true | false`. + /// A boolean: `true`, `false`. Bool(bool), - /// A number in a function header: `3.14`. - Number(f64), - /// A length in a function header: `12pt`. + /// An integer: `120`. + Int(i64), + /// A floating-point number: `1.2`, `10e-4`. + Float(f64), + /// A length: `12pt`, `3cm`. Length(Length), - /// A hex value in a function header: `#20d82a`. + /// A percentage: `50%`. + /// + /// Note: `50%` is represented as `50.0` here, as in the corresponding + /// [literal]. + /// + /// [literal]: ../ast/enum.Lit.html#variant.Percent + Percent(f64), + /// A hex value: `#20d82a`. Hex(&'s str), - /// A quoted string in a function header: `"..."`. + /// A quoted string: `"..."`. Str(TokenStr<'s>), /// Things that are not valid in the context they appeared in. Invalid(&'s str), } -/// A quoted string in a function header: `"..."`. +/// A quoted string: `"..."`. #[derive(Debug, Copy, Clone, PartialEq)] pub struct TokenStr<'s> { /// The string inside the quotes. @@ -90,7 +102,7 @@ pub struct TokenStr<'s> { pub terminated: bool, } -/// A unicode escape sequence. +/// A unicode escape sequence: `\u{1F5FA}`. #[derive(Debug, Copy, Clone, PartialEq)] pub struct TokenUnicodeEscape<'s> { /// The escape sequence between two braces. @@ -99,7 +111,7 @@ pub struct TokenUnicodeEscape<'s> { pub terminated: bool, } -/// A raw block. +/// A raw block: `` `...` ``. #[derive(Debug, Copy, Clone, PartialEq)] pub struct TokenRaw<'s> { /// The raw text between the backticks. @@ -111,7 +123,7 @@ pub struct TokenRaw<'s> { } impl<'s> Token<'s> { - /// The natural-language name for this token for use in error messages. + /// The natural-language name of this token for use in error messages. pub fn name(self) -> &'static str { match self { Self::Space(_) => "space", @@ -124,6 +136,7 @@ impl<'s> Token<'s> { Self::Underscore => "underscore", Self::Backslash => "backslash", Self::Hashtag => "hashtag", + Self::NonBreakingSpace => "non-breaking space", Self::Raw { .. } => "raw block", Self::UnicodeEscape { .. } => "unicode escape sequence", @@ -144,8 +157,10 @@ impl<'s> Token<'s> { Self::Ident(_) => "identifier", Self::Bool(_) => "bool", - Self::Number(_) => "number", + Self::Int(_) => "integer", + Self::Float(_) => "float", Self::Length(_) => "length", + Self::Percent(_) => "percentage", Self::Hex(_) => "hex value", Self::Str { .. } => "string",