From 6d7e7d945b315469b80bca3466a96534b2a17639 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 17 Aug 2020 23:45:03 +0200 Subject: [PATCH] =?UTF-8?q?Tidy=20up=20library=20functions=20=F0=9F=A7=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compute/scope.rs | 6 ---- src/compute/table.rs | 26 ++++++++------ src/compute/value.rs | 35 ++++++++++++++----- src/layout/tree.rs | 6 ++-- src/library/align.rs | 40 +++++++++++++--------- src/library/boxed.rs | 23 +++++++------ src/library/font.rs | 78 +++++++++++++++++++++++------------------- src/library/mod.rs | 14 ++++---- src/library/page.rs | 58 ++++++++++++++++++------------- src/library/spacing.rs | 21 +++++++----- 10 files changed, 177 insertions(+), 130 deletions(-) diff --git a/src/compute/scope.rs b/src/compute/scope.rs index 1fd4db0b2..1c297fde3 100644 --- a/src/compute/scope.rs +++ b/src/compute/scope.rs @@ -31,12 +31,6 @@ impl Scope { self.functions.get(name) } - /// Return the function with the given name or the fallback if there is no - /// such function. - pub fn func_or_fallback(&self, name: &str) -> &FuncValue { - self.func(name).unwrap_or_else(|| self.fallback()) - } - /// Return the fallback function. pub fn fallback(&self) -> &FuncValue { &self.fallback diff --git a/src/compute/table.rs b/src/compute/table.rs index 75effd60a..e8c4b307a 100644 --- a/src/compute/table.rs +++ b/src/compute/table.rs @@ -1,7 +1,7 @@ //! A key-value map that can also model array-like structures. use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Index; use crate::syntax::span::{Span, Spanned}; @@ -180,25 +180,31 @@ impl Debug for Table { let mut builder = f.debug_tuple(""); - struct Entry<'a>(&'a dyn Debug, &'a dyn Debug); + struct Entry<'a>(bool, &'a dyn Display, &'a dyn Debug); impl<'a> Debug for Entry<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f)?; + if self.0 { + f.write_str("\"")?; + } + self.1.fmt(f)?; + if self.0 { + f.write_str("\"")?; + } if f.alternate() { f.write_str(" = ")?; } else { f.write_str("=")?; } - self.1.fmt(f) + self.2.fmt(f) } } for (key, value) in self.nums() { - builder.field(&Entry(&key, &value)); + builder.field(&Entry(false, &key, &value)); } for (key, value) in self.strs() { - builder.field(&Entry(&key, &value)); + builder.field(&Entry(key.contains(' '), &key, &value)); } builder.finish() @@ -358,21 +364,21 @@ mod tests { #[test] fn test_table_format_debug() { let mut table = Table::new(); - assert_eq!(format!("{:?}", table), r#"()"#); - assert_eq!(format!("{:#?}", table), r#"()"#); + assert_eq!(format!("{:?}", table), "()"); + assert_eq!(format!("{:#?}", table), "()"); table.insert(10, "hello"); table.insert("twenty", "there"); table.insert("sp ace", "quotes"); assert_eq!( format!("{:?}", table), - r#"(10="hello", "sp ace"="quotes", "twenty"="there")"#, + r#"(10="hello", "sp ace"="quotes", twenty="there")"#, ); assert_eq!(format!("{:#?}", table).lines().collect::>(), [ "(", r#" 10 = "hello","#, r#" "sp ace" = "quotes","#, - r#" "twenty" = "there","#, + r#" twenty = "there","#, ")", ]); } diff --git a/src/compute/value.rs b/src/compute/value.rs index daa3b17be..32f2778b5 100644 --- a/src/compute/value.rs +++ b/src/compute/value.rs @@ -1,6 +1,7 @@ //! Computational values: Syntactical expressions can be evaluated into these. use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; use std::rc::Rc; use fontdock::{FontStyle, FontWeight, FontWidth}; @@ -13,7 +14,7 @@ use crate::syntax::span::{Span, Spanned}; use crate::syntax::tree::SyntaxTree; use crate::syntax::Ident; use crate::{DynFuture, Feedback, Pass}; -use super::table::{BorrowedKey, SpannedEntry, Table}; +use super::table::{SpannedEntry, Table}; /// A computational value. #[derive(Clone)] @@ -110,7 +111,7 @@ impl PartialEq for Value { /// /// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable. pub type FuncValue = Rc< - dyn Fn(TableValue, LayoutContext<'_>) -> DynFuture> + dyn Fn(Span, TableValue, LayoutContext<'_>) -> DynFuture> >; /// A table of values. @@ -138,13 +139,21 @@ impl TableValue { /// Retrieve and remove the matching value with the lowest number key, /// removing and generating errors for all non-matching entries with lower /// keys. - pub fn expect(&mut self, f: &mut Feedback) -> Option { + /// + /// Generates an error at `err_span` when no matching value was found. + pub fn expect( + &mut self, + name: &str, + span: Span, + f: &mut Feedback, + ) -> 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) { return Some(val); } } + error!(@f, span, "missing argument: {}", name); None } @@ -152,9 +161,8 @@ impl TableValue { /// there is any. /// /// Generates an error if the key exists but the value does not match. - pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option + pub fn take_key<'a, T>(&mut self, key: &str, f: &mut Feedback) -> Option where - K: Into>, T: TryFromValue, { self.remove(key).and_then(|entry| { @@ -312,6 +320,14 @@ impl_match!(ScaleLength, "number or length", /// `Into`. pub struct StringLike(pub String); +impl Deref for StringLike { + type Target = str; + + fn deref(&self) -> &str { + self.0.as_str() + } +} + impl From for String { fn from(like: StringLike) -> String { like.0 @@ -441,7 +457,10 @@ mod tests { table.insert(1, entry(Value::Bool(false))); table.insert(3, entry(Value::Str("hi".to_string()))); table.insert(5, entry(Value::Bool(true))); - assert_eq!(table.expect::(&mut f), Some("hi".to_string())); + assert_eq!( + table.expect::("", Span::ZERO, &mut f), + Some("hi".to_string()) + ); assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected string, found bool")]); assert_eq!(table.len(), 1); } @@ -452,8 +471,8 @@ mod tests { let mut table = Table::new(); table.insert(1, entry(Value::Bool(false))); table.insert("hi", entry(Value::Bool(true))); - assert_eq!(table.take_with_key::<_, bool>(1, &mut f), Some(false)); - assert_eq!(table.take_with_key::<_, f64>("hi", &mut f), None); + assert_eq!(table.take::(), Some(false)); + assert_eq!(table.take_key::("hi", &mut f), None); assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]); assert!(table.is_empty()); } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 092ba5828..f132a8cb6 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -3,7 +3,7 @@ use crate::compute::value::Value; use crate::style::LayoutStyle; use crate::syntax::decoration::Decoration; -use crate::syntax::span::{Offset, Span, Spanned}; +use crate::syntax::span::{Span, Spanned}; use crate::syntax::tree::{CallExpr, SyntaxNode, SyntaxTree}; use crate::{DynFuture, Feedback, Pass}; use super::line::{LineContext, LineLayouter}; @@ -104,7 +104,7 @@ impl<'a> TreeLayouter<'a> { async fn layout_call(&mut self, call: Spanned<&CallExpr>) { let name = call.v.name.v.as_str(); - let span = call.v.name.span.offset(call.span.start); + let span = call.v.name.span; let (func, deco) = if let Some(func) = self.ctx.scope.func(name) { (func, Decoration::Resolved) @@ -116,7 +116,7 @@ impl<'a> TreeLayouter<'a> { self.feedback.decorations.push(Spanned::new(deco, span)); let args = call.v.args.eval(); - let pass = func(args, LayoutContext { + let pass = func(span, args, LayoutContext { style: &self.style, spaces: self.layouter.remaining(), root: true, diff --git a/src/library/align.rs b/src/library/align.rs index c716faefb..14692ecaf 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -10,28 +10,30 @@ use super::*; /// - `vertical`: Any of `top`, `bottom` or `center`. /// /// There may not be two alignment specifications for the same axis. -pub async fn align(mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { +pub async fn align(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); let content = args.take::(); - let aligns: Vec<_> = args.take_all_num_vals::>().collect(); - let h = args.take_with_key::<_, Spanned>("horizontal", &mut f); - let v = args.take_with_key::<_, Spanned>("vertical", &mut f); - args.unexpected(&mut f); - ctx.base = ctx.spaces[0].size; - - let axes = ctx.axes; - let all = aligns.iter() - .map(|align| { - let spec = align.v.axis().unwrap_or(axes.primary.axis()); - (spec, align) - }) - .chain(h.iter().map(|align| (Horizontal, align))) - .chain(v.iter().map(|align| (Vertical, align))); + let h = args.take_key::>("horizontal", &mut f); + let v = args.take_key::>("vertical", &mut f); + let all = args + .take_all_num_vals::>() + .map(|align| (align.v.axis(), align)) + .chain(h.into_iter().map(|align| (Some(Horizontal), align))) + .chain(v.into_iter().map(|align| (Some(Vertical), align))); let mut had = [false; 2]; for (axis, align) in all { + let axis = axis.unwrap_or_else(|| align.v.axis().unwrap_or_else(|| { + let primary = ctx.axes.primary.axis(); + if !had[primary as usize] { + primary + } else { + ctx.axes.secondary.axis() + } + })); + if align.v.axis().map(|a| a != axis).unwrap_or(false) { error!( @f, align.span, @@ -47,12 +49,16 @@ pub async fn align(mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { + ctx.base = ctx.spaces[0].size; let layouted = layout(&tree, ctx).await; f.extend(layouted.feedback); vec![AddMultiple(layouted.output)] } None => vec![SetAlignment(ctx.align)], - }, f) + }; + + args.unexpected(&mut f); + Pass::commands(commands, f) } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index c03043aee..5c727bb69 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -6,32 +6,33 @@ use super::*; /// # Keyword arguments /// - `width`: The width of the box (length of relative to parent's width). /// - `height`: The height of the box (length of relative to parent's height). -pub async fn boxed(mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { +pub async fn boxed(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); - let content = args.take::().unwrap_or(SyntaxTree::new()); - let width = args.take_with_key::<_, ScaleLength>("width", &mut f); - let height = args.take_with_key::<_, ScaleLength>("height", &mut f); - args.unexpected(&mut f); + let content = args.take::().unwrap_or(SyntaxTree::new()); + + ctx.base = ctx.spaces[0].size; ctx.spaces.truncate(1); ctx.repeat = false; - width.with(|v| { - let length = v.raw_scaled(ctx.base.x); + if let Some(w) = args.take_key::("width", &mut f) { + let length = w.raw_scaled(ctx.base.x); ctx.base.x = length; ctx.spaces[0].size.x = length; ctx.spaces[0].expansion.horizontal = true; - }); + } - height.with(|v| { - let length = v.raw_scaled(ctx.base.y); + if let Some(h) = args.take_key::("height", &mut f) { + let length = h.raw_scaled(ctx.base.y); ctx.base.y = length; ctx.spaces[0].size.y = length; ctx.spaces[0].expansion.vertical = true; - }); + } let layouted = layout(&content, ctx).await; let layout = layouted.output.into_iter().next().unwrap(); f.extend(layouted.feedback); + + args.unexpected(&mut f); Pass::commands(vec![Add(layout)], f) } diff --git a/src/library/font.rs b/src/library/font.rs index 71e9552f3..ae0595126 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -18,60 +18,66 @@ use super::*; /// ```typst /// serif = ("Source Serif Pro", "Noto Serif") /// ``` -pub async fn font(mut args: TableValue, ctx: LayoutContext<'_>) -> Pass { +pub async fn font(_: Span, mut args: TableValue, ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); + let mut text = ctx.style.text.clone(); + let mut updated_fallback = false; let content = args.take::(); - let size = args.take::(); - let style = args.take_with_key::<_, FontStyle>("style", &mut f); - let weight = args.take_with_key::<_, FontWeight>("weight", &mut f); - let width = args.take_with_key::<_, FontWidth>("width", &mut f); - let list: Vec<_> = args.take_all_num_vals::() - .map(|s| s.0.to_lowercase()) - .collect(); - let classes: Vec<(_, Vec<_>)> = args.take_all_str::() - .map(|(class, mut table)| { - let fallback = table.take_all_num_vals::() - .map(|s| s.0.to_lowercase()) - .collect(); - (class, fallback) - }) - .collect(); - args.unexpected(&mut f); - - let mut text = ctx.style.text.clone(); - - size.with(|s| match s { - ScaleLength::Absolute(length) => { - text.base_font_size = length.as_raw(); - text.font_scale = 1.0; + 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, } - ScaleLength::Scaled(scale) => text.font_scale = scale, - }); + } - style.with(|s| text.variant.style = s); - weight.with(|w| text.variant.weight = w); - width.with(|w| text.variant.width = w); + let list: Vec<_> = args.take_all_num_vals::() + .map(|s| s.to_lowercase()) + .collect(); if !list.is_empty() { - *text.fallback.list_mut() = list.iter() + *text.fallback.list_mut() = list; + updated_fallback = true; + } + + if let Some(style) = args.take_key::("style", &mut f) { + text.variant.style = style; + } + + if let Some(weight) = args.take_key::("weight", &mut f) { + text.variant.weight = weight; + } + + if let Some(width) = args.take_key::("width", &mut f) { + text.variant.width = width; + } + + for (class, mut table) in args.take_all_str::() { + let fallback = table.take_all_num_vals::() .map(|s| s.to_lowercase()) .collect(); + + text.fallback.set_class_list(class, fallback); + updated_fallback = true; } - for (class, fallback) in classes { - text.fallback.set_class_list(class.clone(), fallback.clone()); + if updated_fallback { + text.fallback.flatten(); } - text.fallback.flatten(); - - Pass::commands(match content { + let commands = match content { Some(tree) => vec![ SetTextStyle(text), LayoutSyntaxTree(tree), SetTextStyle(ctx.style.text.clone()), ], None => vec![SetTextStyle(text)], - }, f) + }; + + args.unexpected(&mut f); + Pass::commands(commands, f) } diff --git a/src/library/mod.rs b/src/library/mod.rs index 1999ba311..eaab72fca 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -30,7 +30,7 @@ macro_rules! std { macro_rules! wrap { ($func:expr) => { - Rc::new(|args, ctx| Box::pin($func(args, ctx))) + Rc::new(|name, args, ctx| Box::pin($func(name, args, ctx))) }; } @@ -51,14 +51,16 @@ std! { /// /// This is also the fallback function, which is used when a function name /// cannot be resolved. -pub async fn val(mut args: TableValue, _: LayoutContext<'_>) -> Pass { - Pass::commands(match args.take::() { +pub async fn val(_: Span, mut args: TableValue, _: LayoutContext<'_>) -> Pass { + let commands = match args.take::() { Some(tree) => vec![LayoutSyntaxTree(tree)], None => vec![], - }, Feedback::new()) + }; + + Pass::commands(commands, Feedback::new()) } -/// `dump`: Dumps its arguments. -pub async fn dump(args: TableValue, _: LayoutContext<'_>) -> Pass { +/// `dump`: Dumps its arguments into the document. +pub async fn dump(_: Span, args: TableValue, _: LayoutContext<'_>) -> Pass { Pass::okay(Value::Table(args)) } diff --git a/src/library/page.rs b/src/library/page.rs index 42f29dbb7..f6f9c1c82 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -16,45 +16,55 @@ use super::*; /// - `top`: The top margin (length or relative to height). /// - `bottom`: The bottom margin (length or relative to height). /// - `flip`: Flips custom or paper-defined width and height (boolean). -pub async fn page(mut args: TableValue, ctx: LayoutContext<'_>) -> Pass { +pub async fn page(_: Span, mut args: TableValue, ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); - let paper = args.take::(); - let width = args.take_with_key::<_, Length>("width", &mut f); - let height = args.take_with_key::<_, Length>("height", &mut f); - let margins = args.take_with_key::<_, ScaleLength>("margins", &mut f); - let left = args.take_with_key::<_, ScaleLength>("left", &mut f); - let right = args.take_with_key::<_, ScaleLength>("right", &mut f); - let top = args.take_with_key::<_, ScaleLength>("top", &mut f); - let bottom = args.take_with_key::<_, ScaleLength>("bottom", &mut f); - let flip = args.take_with_key::<_, bool>("flip", &mut f).unwrap_or(false); - args.unexpected(&mut f); - let mut style = ctx.style.page; - if let Some(paper) = paper { + if let Some(paper) = args.take::() { style.class = paper.class; style.size = paper.size(); - } else if width.is_some() || height.is_some() { - style.class = PaperClass::Custom; } - width.with(|v| style.size.x = v.as_raw()); - height.with(|v| style.size.y = v.as_raw()); - margins.with(|v| style.margins.set_all(Some(v))); - left.with(|v| style.margins.left = Some(v)); - right.with(|v| style.margins.right = Some(v)); - top.with(|v| style.margins.top = Some(v)); - bottom.with(|v| style.margins.bottom = Some(v)); + if let Some(width) = args.take_key::("width", &mut f) { + style.class = PaperClass::Custom; + style.size.x = width.as_raw(); + } - if flip { + if let Some(height) = args.take_key::("height", &mut f) { + style.class = PaperClass::Custom; + style.size.y = height.as_raw(); + } + + if let Some(margins) = args.take_key::("margins", &mut f) { + style.margins.set_all(Some(margins)); + } + + if let Some(left) = args.take_key::("left", &mut f) { + style.margins.left = Some(left); + } + + if let Some(right) = args.take_key::("right", &mut f) { + style.margins.right = Some(right); + } + + if let Some(top) = args.take_key::("top", &mut f) { + style.margins.top = Some(top); + } + + if let Some(bottom) = args.take_key::("bottom", &mut f) { + style.margins.bottom = Some(bottom); + } + + if args.take_key::("flip", &mut f).unwrap_or(false) { style.size.swap(); } + args.unexpected(&mut f); Pass::commands(vec![SetPageStyle(style)], f) } /// `pagebreak`: Ends the current page. -pub async fn pagebreak(args: TableValue, _: LayoutContext<'_>) -> Pass { +pub async fn pagebreak(_: Span, args: TableValue, _: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); args.unexpected(&mut f); Pass::commands(vec![BreakPage], f) diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 3cd775c9f..91049db84 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -6,32 +6,35 @@ use super::*; /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn h(args: TableValue, ctx: LayoutContext<'_>) -> Pass { - spacing(args, ctx, Horizontal).await +pub async fn h(name: Span, args: TableValue, ctx: LayoutContext<'_>) -> Pass { + spacing(name, args, ctx, Horizontal) } /// `v`: Add vertical spacing. /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn v(args: TableValue, ctx: LayoutContext<'_>) -> Pass { - spacing(args, ctx, Vertical).await +pub async fn v(name: Span, args: TableValue, ctx: LayoutContext<'_>) -> Pass { + spacing(name, args, ctx, Vertical) } -async fn spacing( +fn spacing( + name: Span, mut args: TableValue, ctx: LayoutContext<'_>, axis: SpecAxis, ) -> Pass { let mut f = Feedback::new(); - let spacing = args.expect::(&mut f).map(|s| (axis, s)); - args.unexpected(&mut f); - Pass::commands(if let Some((axis, spacing)) = spacing { + let spacing = args.expect::("spacing", name, &mut f); + let commands = if let Some(spacing) = spacing { let axis = axis.to_generic(ctx.axes); let spacing = spacing.raw_scaled(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] - }, f) + }; + + args.unexpected(&mut f); + Pass::commands(commands, f) }