Make compute functions possible 💻
Ships with the amazing new `rgb` function!
This commit is contained in:
parent
6d7e7d945b
commit
77dac270a8
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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::*;
|
||||
|
@ -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,
|
||||
|
@ -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
26
src/library/color.rs
Normal 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()
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user