diff --git a/src/compute/scope.rs b/src/compute/scope.rs index 1c297fde3..3ab565616 100644 --- a/src/compute/scope.rs +++ b/src/compute/scope.rs @@ -8,16 +8,14 @@ use super::value::FuncValue; /// A map from identifiers to functions. pub struct Scope { functions: HashMap, - fallback: FuncValue, } impl Scope { // Create a new empty scope with a fallback function that is invoked when no // match is found. - pub fn new(fallback: FuncValue) -> Self { + pub fn new() -> Self { Self { functions: HashMap::new(), - fallback, } } @@ -30,11 +28,6 @@ impl Scope { pub fn func(&self, name: &str) -> Option<&FuncValue> { self.functions.get(name) } - - /// Return the fallback function. - pub fn fallback(&self) -> &FuncValue { - &self.fallback - } } impl Debug for Scope { diff --git a/src/compute/table.rs b/src/compute/table.rs index e8c4b307a..bb71d4f28 100644 --- a/src/compute/table.rs +++ b/src/compute/table.rs @@ -115,6 +115,17 @@ impl Table { self.lowest_free += 1; } + /// Iterator over all borrowed keys and values. + pub fn iter(&self) -> impl Iterator { + self.nums().map(|(&k, v)| (BorrowedKey::Num(k), v)) + .chain(self.strs().map(|(k, v)| (BorrowedKey::Str(k), v))) + } + + /// Iterate over all values in the table. + pub fn values(&self) -> impl Iterator { + self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v)) + } + /// Iterate over the number key-value pairs. pub fn nums(&self) -> std::collections::btree_map::Iter { self.nums.iter() @@ -125,9 +136,16 @@ impl Table { self.strs.iter() } - /// Iterate over all values in the table. - pub fn values(&self) -> impl Iterator { - self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v)) + /// Move into an owned iterator over owned keys and values. + pub fn into_iter(self) -> impl Iterator { + self.nums.into_iter().map(|(k, v)| (OwnedKey::Num(k), v)) + .chain(self.strs.into_iter().map(|(k, v)| (OwnedKey::Str(k), v))) + } + + /// Move into an owned iterator over all values in the table. + pub fn into_values(self) -> impl Iterator { + self.nums.into_iter().map(|(_, v)| v) + .chain(self.strs.into_iter().map(|(_, v)| v)) } /// Iterate over the number key-value pairs. @@ -139,12 +157,6 @@ impl Table { pub fn into_strs(self) -> std::collections::btree_map::IntoIter { self.strs.into_iter() } - - /// Move into an owned iterator over all values in the table. - pub fn into_values(self) -> impl Iterator { - self.nums.into_iter().map(|(_, v)| v) - .chain(self.strs.into_iter().map(|(_, v)| v)) - } } impl<'a, K, V> Index for Table @@ -168,7 +180,7 @@ impl Eq for Table {} impl PartialEq for Table { fn eq(&self, other: &Self) -> bool { - self.nums().eq(other.nums()) && self.strs().eq(other.strs()) + self.iter().eq(other.iter()) } } @@ -218,6 +230,15 @@ pub enum OwnedKey { Str(String), } +impl From> for OwnedKey { + fn from(key: BorrowedKey<'_>) -> Self { + match key { + BorrowedKey::Num(num) => Self::Num(num), + BorrowedKey::Str(string) => Self::Str(string.to_string()), + } + } +} + impl From for OwnedKey { fn from(num: u64) -> Self { Self::Num(num) diff --git a/src/compute/value.rs b/src/compute/value.rs index 32f2778b5..c11a3d316 100644 --- a/src/compute/value.rs +++ b/src/compute/value.rs @@ -7,11 +7,11 @@ use std::rc::Rc; use fontdock::{FontStyle, FontWeight, FontWidth}; use crate::color::RgbaColor; -use crate::layout::{Commands, Dir, LayoutContext, SpecAlign}; +use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign}; use crate::length::{Length, ScaleLength}; use crate::paper::Paper; use crate::syntax::span::{Span, Spanned}; -use crate::syntax::tree::SyntaxTree; +use crate::syntax::tree::{SyntaxTree, SyntaxNode}; use crate::syntax::Ident; use crate::{DynFuture, Feedback, Pass}; use super::table::{SpannedEntry, Table}; @@ -61,6 +61,47 @@ impl Value { } } +impl Spanned { + /// Transform this value into something layoutable. + /// + /// If this is already a command-value, it is simply unwrapped, otherwise + /// the value is represented as layoutable content in a reasonable way. + pub fn into_commands(self) -> Commands { + match self.v { + Value::Commands(commands) => commands, + Value::Tree(tree) => vec![Command::LayoutSyntaxTree(tree)], + + // Forward to each entry, separated with spaces. + Value::Table(table) => { + let mut commands = vec![]; + let mut end = None; + for entry in table.into_values() { + if let Some(last_end) = end { + let span = Span::new(last_end, entry.key.start); + commands.push(Command::LayoutSyntaxTree(vec![ + Spanned::new(SyntaxNode::Spacing, span) + ])); + } + + end = Some(entry.val.span.end); + commands.extend(entry.val.into_commands()); + } + commands + } + + // Format with debug. + val => vec![ + Command::LayoutSyntaxTree(vec![ + Spanned::new( + SyntaxNode::Text(format!("{:?}", val)), + self.span, + ) + ]) + ], + } + } +} + impl Debug for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use Value::*; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 837c19ec6..efb2f169f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -45,7 +45,7 @@ pub struct BoxLayout { } /// The context for layouting. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LayoutContext<'a> { /// The font loader to query fonts from when typesetting text. pub loader: &'a SharedFontLoader, diff --git a/src/layout/tree.rs b/src/layout/tree.rs index f132a8cb6..e500c4ba2 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,6 +1,5 @@ //! Layouting of syntax trees. -use crate::compute::value::Value; use crate::style::LayoutStyle; use crate::syntax::decoration::Decoration; use crate::syntax::span::{Span, Spanned}; @@ -62,12 +61,7 @@ impl<'a> TreeLayouter<'a> { }; match &node.v { - SyntaxNode::Spacing => { - self.layouter.add_primary_spacing( - self.style.text.word_spacing(), - SpacingKind::WORD, - ); - } + SyntaxNode::Spacing => self.layout_space(), SyntaxNode::Linebreak => self.layouter.finish_line(), SyntaxNode::ToggleItalic => { @@ -93,45 +87,11 @@ impl<'a> TreeLayouter<'a> { } } - async fn layout_par(&mut self, par: &SyntaxTree) { - self.layouter.add_secondary_spacing( - self.style.text.paragraph_spacing(), - SpacingKind::PARAGRAPH, + fn layout_space(&mut self) { + self.layouter.add_primary_spacing( + self.style.text.word_spacing(), + SpacingKind::WORD, ); - - self.layout_tree(par).await; - } - - async fn layout_call(&mut self, call: Spanned<&CallExpr>) { - let name = call.v.name.v.as_str(); - let span = call.v.name.span; - - let (func, deco) = if let Some(func) = self.ctx.scope.func(name) { - (func, Decoration::Resolved) - } else { - error!(@self.feedback, span, "unknown function"); - (self.ctx.scope.fallback(), Decoration::Unresolved) - }; - - self.feedback.decorations.push(Spanned::new(deco, span)); - - let args = call.v.args.eval(); - let pass = func(span, args, LayoutContext { - style: &self.style, - spaces: self.layouter.remaining(), - root: true, - ..self.ctx - }).await; - - self.feedback.extend(pass.feedback); - - if let Value::Commands(commands) = pass.output { - for command in commands { - self.execute_command(command, call.span).await; - } - } else { - self.layout_raw(&[format!("{:?}", pass.output)]).await; - } } async fn layout_text(&mut self, text: &str) { @@ -154,24 +114,44 @@ impl<'a> TreeLayouter<'a> { self.style.text.fallback .list_mut() .insert(0, "monospace".to_string()); - self.style.text.fallback.flatten(); - // Layout the first line. - let mut iter = lines.iter(); - if let Some(line) = iter.next() { - self.layout_text(line).await; - } - - // Put a newline before each following line. - for line in iter { - self.layouter.finish_line(); + let mut first = true; + for line in lines { + if !first { + self.layouter.finish_line(); + } + first = false; self.layout_text(line).await; } self.style.text.fallback = fallback; } + async fn layout_par(&mut self, par: &SyntaxTree) { + self.layout_tree(par).await; + self.layouter.add_secondary_spacing( + self.style.text.paragraph_spacing(), + SpacingKind::PARAGRAPH, + ); + } + + async fn layout_call(&mut self, call: Spanned<&CallExpr>) { + let ctx = LayoutContext { + style: &self.style, + spaces: self.layouter.remaining(), + root: false, + ..self.ctx + }; + + let val = call.v.eval(&ctx, &mut self.feedback).await; + let commands = Spanned::new(val, call.span).into_commands(); + + for command in commands { + self.execute_command(command, call.span).await; + } + } + async fn execute_command(&mut self, command: Command, span: Span) { use Command::*; diff --git a/src/library/color.rs b/src/library/color.rs new file mode 100644 index 000000000..eab84fd01 --- /dev/null +++ b/src/library/color.rs @@ -0,0 +1,26 @@ +use crate::color::RgbaColor; +use super::*; + +/// `rgb`: Create an RGB(A) color. +pub async fn rgb(span: Span, mut args: TableValue, _: LayoutContext<'_>) -> Pass { + let mut f = Feedback::new(); + + let color = RgbaColor::new( + clamp(args.expect::>("red value", span, &mut f), &mut f), + clamp(args.expect::>("green value", span, &mut f), &mut f), + clamp(args.expect::>("blue value", span, &mut f), &mut f), + clamp(args.take::>(), &mut f), + ); + + args.unexpected(&mut f); + Pass::new(Value::Color(color), f) +} + +fn clamp(component: Option>, f: &mut Feedback) -> u8 { + 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() +} diff --git a/src/library/mod.rs b/src/library/mod.rs index eaab72fca..7d266eb5f 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -2,12 +2,14 @@ mod align; mod boxed; +mod color; mod font; mod page; mod spacing; pub use align::*; pub use boxed::*; +pub use color::*; pub use font::*; pub use page::*; pub use spacing::*; @@ -18,10 +20,10 @@ use crate::compute::scope::Scope; use crate::prelude::*; macro_rules! std { - (fallback: $fallback:expr $(, $name:literal => $func:expr)* $(,)?) => { + ($($name:literal => $func:expr),* $(,)?) => { /// Create a scope with all standard library functions. pub fn _std() -> Scope { - let mut std = Scope::new(wrap!(val)); + let mut std = Scope::new(); $(std.insert($name, wrap!($func));)* std } @@ -35,32 +37,12 @@ macro_rules! wrap { } std! { - fallback: val, "align" => align, "box" => boxed, - "dump" => dump, "font" => font, "h" => h, "page" => page, "pagebreak" => pagebreak, + "rgb" => rgb, "v" => v, - "val" => val, -} - -/// `val`: Layouts its body flatly, ignoring other arguments. -/// -/// This is also the fallback function, which is used when a function name -/// cannot be resolved. -pub async fn val(_: Span, mut args: TableValue, _: LayoutContext<'_>) -> Pass { - let commands = match args.take::() { - Some(tree) => vec![LayoutSyntaxTree(tree)], - None => vec![], - }; - - Pass::commands(commands, Feedback::new()) -} - -/// `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/syntax/tokens.rs b/src/syntax/tokens.rs index 2d371bf83..a27ef9823 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -252,10 +252,9 @@ impl<'s> Iterator for Tokens<'s> { let text = self.read_string_until(|n| { let val = match n { c if c.is_whitespace() => true, - '[' | ']' | '/' | '*' => true, + '[' | ']' | '{' | '}' | '/' | '*' => true, '\\' | '_' | '`' if body => true, - ':' | '=' | ',' | '"' | - '(' | ')' | '{' | '}' if !body => true, + ':' | '=' | ',' | '"' | '(' | ')' if !body => true, '+' | '-' if !body && !last_was_e => true, _ => false, }; diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index e7a1eaf14..47c79117b 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -5,7 +5,10 @@ use std::fmt::{self, Debug, Formatter}; use crate::color::RgbaColor; use crate::compute::table::{SpannedEntry, Table}; use crate::compute::value::{TableValue, Value}; +use crate::layout::LayoutContext; use crate::length::Length; +use crate::{DynFuture, Feedback}; +use super::decoration::Decoration; use super::span::{Spanned, SpanVec}; use super::Ident; @@ -91,7 +94,11 @@ impl Expr { } /// Evaluate the expression to a value. - pub fn eval(&self) -> Value { + pub async fn eval( + &self, + ctx: &LayoutContext<'_>, + f: &mut Feedback, + ) -> Value { use Expr::*; match self { Ident(i) => Value::Ident(i.clone()), @@ -100,9 +107,9 @@ impl Expr { &Number(n) => Value::Number(n), &Length(s) => Value::Length(s), &Color(c) => Value::Color(c), - Table(t) => Value::Table(t.eval()), + Table(t) => Value::Table(t.eval(ctx, f).await), Tree(t) => Value::Tree(t.clone()), - Call(_) => todo!("eval call"), + Call(call) => call.eval(ctx, f).await, Neg(_) => todo!("eval neg"), Add(_, _) => todo!("eval add"), Sub(_, _) => todo!("eval sub"), @@ -144,18 +151,23 @@ pub type TableExpr = Table>; impl TableExpr { /// Evaluate the table expression to a table value. - pub fn eval(&self) -> TableValue { - let mut table = TableValue::new(); + pub fn eval<'a>( + &'a self, + ctx: &'a LayoutContext<'a>, + f: &'a mut Feedback, + ) -> DynFuture<'a, TableValue> { + Box::pin(async move { + let mut table = TableValue::new(); - for (&key, entry) in self.nums() { - table.insert(key, entry.as_ref().map(|val| val.eval())); - } + for (key, entry) in self.iter() { + let val = entry.val.v.eval(ctx, f).await; + let spanned = Spanned::new(val, entry.val.span); + let entry = SpannedEntry::new(entry.key, spanned); + table.insert(key, entry); + } - for (key, entry) in self.strs() { - table.insert(key.clone(), entry.as_ref().map(|val| val.eval())); - } - - table + table + }) } } @@ -165,3 +177,23 @@ pub struct CallExpr { pub name: Spanned, pub args: TableExpr, } + +impl CallExpr { + /// Evaluate the call expression to a value. + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + let name = self.name.v.as_str(); + let span = self.name.span; + let args = self.args.eval(ctx, f).await; + + if let Some(func) = ctx.scope.func(name) { + let pass = func(span, args, ctx.clone()).await; + f.extend(pass.feedback); + f.decorations.push(Spanned::new(Decoration::Resolved, span)); + pass.output + } else { + error!(@f, span, "unknown function"); + f.decorations.push(Spanned::new(Decoration::Unresolved, span)); + Value::Table(args) + } + } +}