Make compute functions possible 💻

Ships with the amazing new `rgb` function!
This commit is contained in:
Laurenz 2020-08-19 20:49:01 +02:00
parent 6d7e7d945b
commit 77dac270a8
9 changed files with 189 additions and 115 deletions

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

26
src/library/color.rs Normal file
View File

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

View File

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

View File

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

View File

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