Int, Float, Relative and Linear values 🍉

This commit is contained in:
Laurenz 2020-10-03 15:07:57 +02:00
parent 0fc25d732d
commit 95bae5725c
17 changed files with 461 additions and 262 deletions

View File

@ -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)
}
}

View File

@ -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));
}
}

View File

@ -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<T> Sides<T> {
}
}
}
/// 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<f64> for Linear {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self {
rel: self.rel + other,
abs: self.abs + other,
}
}
}
impl MulAssign<f64> for Linear {
fn mul_assign(&mut self, other: f64) {
self.rel *= other;
self.abs *= other;
}
}
impl Mul<Linear> for f64 {
type Output = Linear;
fn mul(self, other: Linear) -> Linear {
Linear {
rel: self + other.rel,
abs: self + other.abs,
}
}
}
impl Div<f64> for Linear {
type Output = Self;
fn div(self, other: f64) -> Self {
Self {
rel: self.rel / other,
abs: self.abs / other,
}
}
}
impl DivAssign<f64> 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)
}
}

View File

@ -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();

View File

@ -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::*;

View File

@ -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::<ScaleLength>("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::<Linear>("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::<ScaleLength>("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::<Linear>("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;
}

View File

@ -5,20 +5,18 @@ use crate::color::RgbaColor;
pub async fn rgb(span: Span, mut args: DictValue, _: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
let r = args.expect::<Spanned<f64>>("red value", span, &mut f);
let g = args.expect::<Spanned<f64>>("green value", span, &mut f);
let b = args.expect::<Spanned<f64>>("blue value", span, &mut f);
let a = args.take::<Spanned<f64>>();
let r = args.expect::<Spanned<i64>>("red value", span, &mut f);
let g = args.expect::<Spanned<i64>>("green value", span, &mut f);
let b = args.expect::<Spanned<i64>>("blue value", span, &mut f);
let a = args.take::<Spanned<i64>>();
let mut clamp = |component: Option<Spanned<f64>>, 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<Spanned<i64>>, 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));

View File

@ -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::<SynTree>();
if let Some(s) = args.take::<ScaleLength>() {
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::<Linear>() {
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;
}
}

View File

@ -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::<Length>("width", &mut f) {
if let Some(Abs(width)) = args.take_key::<Abs>("width", &mut f) {
style.class = PaperClass::Custom;
style.size.width = width.as_raw();
style.size.width = width;
}
if let Some(height) = args.take_key::<Length>("height", &mut f) {
if let Some(Abs(height)) = args.take_key::<Abs>("height", &mut f) {
style.class = PaperClass::Custom;
style.size.height = height.as_raw();
style.size.height = height;
}
if let Some(margins) = args.take_key::<ScaleLength>("margins", &mut f) {
if let Some(margins) = args.take_key::<Linear>("margins", &mut f) {
style.margins = Sides::uniform(Some(margins));
}
if let Some(left) = args.take_key::<ScaleLength>("left", &mut f) {
if let Some(left) = args.take_key::<Linear>("left", &mut f) {
style.margins.left = Some(left);
}
if let Some(top) = args.take_key::<ScaleLength>("top", &mut f) {
if let Some(top) = args.take_key::<Linear>("top", &mut f) {
style.margins.top = Some(top);
}
if let Some(right) = args.take_key::<ScaleLength>("right", &mut f) {
if let Some(right) = args.take_key::<Linear>("right", &mut f) {
style.margins.right = Some(right);
}
if let Some(bottom) = args.take_key::<ScaleLength>("bottom", &mut f) {
if let Some(bottom) = args.take_key::<Linear>("bottom", &mut f) {
style.margins.bottom = Some(bottom);
}

View File

@ -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<Value> {
let mut f = Feedback::new();
let spacing = args.expect::<ScaleLength>("spacing", name, &mut f);
let spacing = args.expect::<Linear>("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![]

View File

@ -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<ScaleLength> {
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<Linear> {
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),
}
}
}

View File

@ -72,6 +72,7 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Spanned<SynNode>> {
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<Expr> {
// 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))),

View File

@ -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"));

View File

@ -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::<bool>() {
Token::Bool(b)
} else if let Ok(int) = text.parse::<i64>() {
Token::Int(int)
} else if let Ok(num) = text.parse::<f64>() {
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::<Length>() {
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));
}
}

View File

@ -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<Option<ScaleLength>>,
pub margins: Sides<Option<Linear>>,
}
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),
}
}
}

View File

@ -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),

View File

@ -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 `//<str>\n`.
LineComment(&'s str),
/// A block comment with inner string contents `/*<str>*/`. The comment
/// can contain nested block comments.
/// A block comment with inner string contents `/*<str>*/`.
///
/// 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",