summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-17 23:45:03 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-17 23:45:03 +0200
commit6d7e7d945b315469b80bca3466a96534b2a17639 (patch)
tree1b6c5e0ae7fb683ff7f3b6b1d961151a8e467a80 /src
parent3cbca56a7195bb2a7996530d584300d697c11dc8 (diff)
Tidy up library functions 🧺
Diffstat (limited to 'src')
-rw-r--r--src/compute/scope.rs6
-rw-r--r--src/compute/table.rs26
-rw-r--r--src/compute/value.rs35
-rw-r--r--src/layout/tree.rs6
-rw-r--r--src/library/align.rs40
-rw-r--r--src/library/boxed.rs21
-rw-r--r--src/library/font.rs74
-rw-r--r--src/library/mod.rs14
-rw-r--r--src/library/page.rs56
-rw-r--r--src/library/spacing.rs21
10 files changed, 173 insertions, 126 deletions
diff --git a/src/compute/scope.rs b/src/compute/scope.rs
index 1fd4db0b..1c297fde 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 75effd60..e8c4b307 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<V: Debug> Debug for Table<V> {
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::<Vec<_>>(), [
"(",
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 daa3b17b..32f2778b 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<Pass<Value>>
+ dyn Fn(Span, TableValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>
>;
/// 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<T: TryFromValue>(&mut self, f: &mut Feedback) -> Option<T> {
+ ///
+ /// Generates an error at `err_span` when no matching value was found.
+ pub fn expect<T: TryFromValue>(
+ &mut self,
+ name: &str,
+ span: Span,
+ f: &mut Feedback,
+ ) -> 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) {
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<T>
+ pub fn take_key<'a, T>(&mut self, key: &str, f: &mut Feedback) -> Option<T>
where
- K: Into<BorrowedKey<'a>>,
T: TryFromValue,
{
self.remove(key).and_then(|entry| {
@@ -312,6 +320,14 @@ impl_match!(ScaleLength, "number or length",
/// `Into<String>`.
pub struct StringLike(pub String);
+impl Deref for StringLike {
+ type Target = str;
+
+ fn deref(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
impl From<StringLike> 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::<String>(&mut f), Some("hi".to_string()));
+ assert_eq!(
+ table.expect::<String>("", 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::<bool>(), Some(false));
+ assert_eq!(table.take_key::<f64>("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 092ba582..f132a8cb 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 c716faef..14692eca 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<Value> {
+pub async fn align(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
let content = args.take::<SyntaxTree>();
- let aligns: Vec<_> = args.take_all_num_vals::<Spanned<SpecAlign>>().collect();
- let h = args.take_with_key::<_, Spanned<SpecAlign>>("horizontal", &mut f);
- let v = args.take_with_key::<_, Spanned<SpecAlign>>("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::<Spanned<SpecAlign>>("horizontal", &mut f);
+ let v = args.take_key::<Spanned<SpecAlign>>("vertical", &mut f);
+ let all = args
+ .take_all_num_vals::<Spanned<SpecAlign>>()
+ .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<Val
}
}
- Pass::commands(match content {
+ let commands = match content {
Some(tree) => {
+ 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 c03043ae..5c727bb6 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<Value> {
+pub async fn boxed(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
+
let content = args.take::<SyntaxTree>().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);
+ 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::<ScaleLength>("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::<ScaleLength>("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 71e9552f..ae059512 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<Value> {
+pub async fn font(_: Span, mut args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
+ let mut text = ctx.style.text.clone();
+ let mut updated_fallback = false;
let content = args.take::<SyntaxTree>();
- let size = args.take::<ScaleLength>();
- 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);
+
+ 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,
+ }
+ }
+
let list: Vec<_> = args.take_all_num_vals::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect();
- let classes: Vec<(_, Vec<_>)> = args.take_all_str::<TableValue>()
- .map(|(class, mut table)| {
- let fallback = table.take_all_num_vals::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect();
- (class, fallback)
- })
+ .map(|s| s.to_lowercase())
.collect();
- args.unexpected(&mut f);
+ if !list.is_empty() {
+ *text.fallback.list_mut() = list;
+ updated_fallback = true;
+ }
- let mut text = ctx.style.text.clone();
+ if let Some(style) = args.take_key::<FontStyle>("style", &mut f) {
+ text.variant.style = style;
+ }
- size.with(|s| 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(weight) = args.take_key::<FontWeight>("weight", &mut f) {
+ text.variant.weight = weight;
+ }
- style.with(|s| text.variant.style = s);
- weight.with(|w| text.variant.weight = w);
- width.with(|w| text.variant.width = w);
+ if let Some(width) = args.take_key::<FontWidth>("width", &mut f) {
+ text.variant.width = width;
+ }
- if !list.is_empty() {
- *text.fallback.list_mut() = list.iter()
+ for (class, mut table) in args.take_all_str::<TableValue>() {
+ let fallback = table.take_all_num_vals::<StringLike>()
.map(|s| s.to_lowercase())
.collect();
- }
- for (class, fallback) in classes {
- text.fallback.set_class_list(class.clone(), fallback.clone());
+ text.fallback.set_class_list(class, fallback);
+ updated_fallback = true;
}
- text.fallback.flatten();
+ if updated_fallback {
+ 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 1999ba31..eaab72fc 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<Value> {
- Pass::commands(match args.take::<SyntaxTree>() {
+pub async fn val(_: Span, mut args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
+ let commands = match args.take::<SyntaxTree>() {
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<Value> {
+/// `dump`: Dumps its arguments into the document.
+pub async fn dump(_: Span, args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
Pass::okay(Value::Table(args))
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 42f29dbb..f6f9c1c8 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<Value> {
+pub async fn page(_: Span, mut args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- let paper = args.take::<Paper>();
- 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::<Paper>() {
style.class = paper.class;
style.size = paper.size();
- } else if width.is_some() || height.is_some() {
+ }
+
+ if let Some(width) = args.take_key::<Length>("width", &mut f) {
style.class = PaperClass::Custom;
+ style.size.x = width.as_raw();
}
- 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(height) = args.take_key::<Length>("height", &mut f) {
+ style.class = PaperClass::Custom;
+ style.size.y = height.as_raw();
+ }
+
+ if let Some(margins) = args.take_key::<ScaleLength>("margins", &mut f) {
+ style.margins.set_all(Some(margins));
+ }
- if flip {
+ if let Some(left) = args.take_key::<ScaleLength>("left", &mut f) {
+ style.margins.left = Some(left);
+ }
+
+ if let Some(right) = args.take_key::<ScaleLength>("right", &mut f) {
+ style.margins.right = Some(right);
+ }
+
+ if let Some(top) = args.take_key::<ScaleLength>("top", &mut f) {
+ style.margins.top = Some(top);
+ }
+
+ if let Some(bottom) = args.take_key::<ScaleLength>("bottom", &mut f) {
+ style.margins.bottom = Some(bottom);
+ }
+
+ if args.take_key::<bool>("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<Value> {
+pub async fn pagebreak(_: Span, args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
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 3cd775c9..91049db8 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<Value> {
- spacing(args, ctx, Horizontal).await
+pub async fn h(name: Span, args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
+ 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<Value> {
- spacing(args, ctx, Vertical).await
+pub async fn v(name: Span, args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
+ spacing(name, args, ctx, Vertical)
}
-async fn spacing(
+fn spacing(
+ name: Span,
mut args: TableValue,
ctx: LayoutContext<'_>,
axis: SpecAxis,
) -> Pass<Value> {
let mut f = Feedback::new();
- let spacing = args.expect::<ScaleLength>(&mut f).map(|s| (axis, s));
- args.unexpected(&mut f);
- Pass::commands(if let Some((axis, spacing)) = spacing {
+ let spacing = args.expect::<ScaleLength>("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)
}