summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-19 20:49:01 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-19 20:49:01 +0200
commit77dac270a8a99f24a6fc0eb9e92256bcc07c586c (patch)
tree8e240b798a5c1aabd77c823e65828f3c6d2557f1 /src
parent6d7e7d945b315469b80bca3466a96534b2a17639 (diff)
Make compute functions possible 💻
Ships with the amazing new `rgb` function!
Diffstat (limited to 'src')
-rw-r--r--src/compute/scope.rs9
-rw-r--r--src/compute/table.rs41
-rw-r--r--src/compute/value.rs45
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/tree.rs90
-rw-r--r--src/library/color.rs26
-rw-r--r--src/library/mod.rs28
-rw-r--r--src/syntax/tokens.rs5
-rw-r--r--src/syntax/tree.rs58
9 files changed, 189 insertions, 115 deletions
diff --git a/src/compute/scope.rs b/src/compute/scope.rs
index 1c297fde..3ab56561 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<String, FuncValue>,
- 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 e8c4b307..bb71d4f2 100644
--- a/src/compute/table.rs
+++ b/src/compute/table.rs
@@ -115,6 +115,17 @@ impl<V> Table<V> {
self.lowest_free += 1;
}
+ /// Iterator over all borrowed keys and values.
+ pub fn iter(&self) -> impl Iterator<Item = (BorrowedKey, &V)> {
+ 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<Item = &V> {
+ 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<u64, V> {
self.nums.iter()
@@ -125,9 +136,16 @@ impl<V> Table<V> {
self.strs.iter()
}
- /// Iterate over all values in the table.
- pub fn values(&self) -> impl Iterator<Item = &V> {
- 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<Item = (OwnedKey, V)> {
+ 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<Item = V> {
+ 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<V> Table<V> {
pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
self.strs.into_iter()
}
-
- /// Move into an owned iterator over all values in the table.
- pub fn into_values(self) -> impl Iterator<Item = V> {
- self.nums.into_iter().map(|(_, v)| v)
- .chain(self.strs.into_iter().map(|(_, v)| v))
- }
}
impl<'a, K, V> Index<K> for Table<V>
@@ -168,7 +180,7 @@ impl<V: Eq> Eq for Table<V> {}
impl<V: PartialEq> PartialEq for Table<V> {
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<BorrowedKey<'_>> 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<u64> for OwnedKey {
fn from(num: u64) -> Self {
Self::Num(num)
diff --git a/src/compute/value.rs b/src/compute/value.rs
index 32f2778b..c11a3d31 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<Value> {
+ /// 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 837c19ec..efb2f169 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 f132a8cb..e500c4ba 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 00000000..eab84fd0
--- /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<Value> {
+ let mut f = Feedback::new();
+
+ let color = RgbaColor::new(
+ clamp(args.expect::<Spanned<f64>>("red value", span, &mut f), &mut f),
+ clamp(args.expect::<Spanned<f64>>("green value", span, &mut f), &mut f),
+ clamp(args.expect::<Spanned<f64>>("blue value", span, &mut f), &mut f),
+ clamp(args.take::<Spanned<f64>>(), &mut f),
+ );
+
+ args.unexpected(&mut f);
+ Pass::new(Value::Color(color), f)
+}
+
+fn clamp(component: Option<Spanned<f64>>, 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 eaab72fc..7d266eb5 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<Value> {
- let commands = match args.take::<SyntaxTree>() {
- 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<Value> {
- Pass::okay(Value::Table(args))
}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 2d371bf8..a27ef982 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 e7a1eaf1..47c79117 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<SpannedEntry<Expr>>;
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.strs() {
- table.insert(key.clone(), 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);
+ }
- table
+ table
+ })
}
}
@@ -165,3 +177,23 @@ pub struct CallExpr {
pub name: Spanned<Ident>,
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)
+ }
+ }
+}