summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-03 15:58:15 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-03 15:58:15 +0200
commitd59109e8fffa1d0b03234329e12f5d3e578804e8 (patch)
treefe7453da6f2ae327993e5ca6436ddc6a448a2c41 /src/eval
parentf77f1f61bf05ae506689be3c40252c5807276280 (diff)
Support recursive show rules
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs25
-rw-r--r--src/eval/capture.rs6
-rw-r--r--src/eval/dict.rs12
-rw-r--r--src/eval/func.rs42
-rw-r--r--src/eval/methods.rs2
-rw-r--r--src/eval/mod.rs41
-rw-r--r--src/eval/value.rs18
7 files changed, 88 insertions, 58 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs
index 40454ff5..f507e714 100644
--- a/src/eval/args.rs
+++ b/src/eval/args.rs
@@ -26,19 +26,22 @@ pub struct Arg {
}
impl Args {
+ /// Create empty arguments from a span.
+ pub fn new(span: Span) -> Self {
+ Self { span, items: vec![] }
+ }
+
/// Create positional arguments from a span and values.
pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self {
- Self {
- span,
- items: values
- .into_iter()
- .map(|value| Arg {
- span,
- name: None,
- value: Spanned::new(value, span),
- })
- .collect(),
- }
+ let items = values
+ .into_iter()
+ .map(|value| Arg {
+ span,
+ name: None,
+ value: Spanned::new(value, span),
+ })
+ .collect();
+ Self { span, items }
}
/// Consume and cast the first positional argument.
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index 8e54122f..24fc7abc 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -91,8 +91,10 @@ impl<'a> CapturesVisitor<'a> {
// A show rule contains a binding, but that binding is only active
// after the target has been evaluated.
Some(Expr::Show(show)) => {
- self.visit(show.target().as_red());
- self.bind(show.binding());
+ self.visit(show.pattern().as_red());
+ if let Some(binding) = show.binding() {
+ self.bind(binding);
+ }
self.visit(show.body().as_red());
}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index b630fc63..bda433e1 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -46,8 +46,8 @@ impl Dict {
}
/// Borrow the value the given `key` maps to.
- pub fn get(&self, key: EcoString) -> StrResult<&Value> {
- self.0.get(&key).ok_or_else(|| missing_key(&key))
+ pub fn get(&self, key: &EcoString) -> StrResult<&Value> {
+ self.0.get(key).ok_or_else(|| missing_key(key))
}
/// Mutably borrow the value the given `key` maps to.
@@ -59,7 +59,7 @@ impl Dict {
}
/// Whether the dictionary contains a specific key.
- pub fn contains(&self, key: &str) -> bool {
+ pub fn contains(&self, key: &EcoString) -> bool {
self.0.contains_key(key)
}
@@ -69,10 +69,10 @@ impl Dict {
}
/// Remove a mapping by `key`.
- pub fn remove(&mut self, key: EcoString) -> StrResult<()> {
- match Arc::make_mut(&mut self.0).remove(&key) {
+ pub fn remove(&mut self, key: &EcoString) -> StrResult<()> {
+ match Arc::make_mut(&mut self.0).remove(key) {
Some(_) => Ok(()),
- None => Err(missing_key(&key)),
+ None => Err(missing_key(key)),
}
}
diff --git a/src/eval/func.rs b/src/eval/func.rs
index b2ecfe93..7e4040b6 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -4,9 +4,8 @@ use std::sync::Arc;
use super::{Args, Control, Eval, Scope, Scopes, Value};
use crate::diag::{StrResult, TypResult};
-use crate::model::{Content, StyleMap};
+use crate::model::{Content, NodeId, StyleMap};
use crate::syntax::ast::Expr;
-use crate::syntax::Span;
use crate::util::EcoString;
use crate::Context;
@@ -35,7 +34,7 @@ impl Func {
name,
func,
set: None,
- show: None,
+ node: None,
})))
}
@@ -49,15 +48,7 @@ impl Func {
Ok(Value::Content(content.styled_with_map(styles.scoped())))
},
set: Some(T::set),
- show: if T::SHOWABLE {
- Some(|recipe, span| {
- let mut styles = StyleMap::new();
- styles.set_recipe::<T>(recipe, span);
- styles
- })
- } else {
- None
- },
+ node: T::SHOWABLE.then(|| NodeId::of::<T>()),
})))
}
@@ -80,7 +71,20 @@ impl Func {
}
}
- /// Call the function with a virtual machine and arguments.
+ /// The number of positional arguments this function takes, if known.
+ pub fn argc(&self) -> Option<usize> {
+ match self.0.as_ref() {
+ Repr::Closure(closure) => Some(
+ closure.params.iter().filter(|(_, default)| default.is_none()).count(),
+ ),
+ Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
+ applied.items.iter().filter(|arg| arg.name.is_none()).count(),
+ )),
+ _ => None,
+ }
+ }
+
+ /// Call the function with the given arguments.
pub fn call(&self, ctx: &mut Context, mut args: Args) -> TypResult<Value> {
let value = match self.0.as_ref() {
Repr::Native(native) => (native.func)(ctx, &mut args)?,
@@ -104,10 +108,10 @@ impl Func {
Ok(styles)
}
- /// Execute the function's show rule.
- pub fn show(&self, recipe: Func, span: Span) -> StrResult<StyleMap> {
+ /// The id of the node to customize with this function's show rule.
+ pub fn node(&self) -> StrResult<NodeId> {
match self.0.as_ref() {
- Repr::Native(Native { show: Some(show), .. }) => Ok(show(recipe, span)),
+ Repr::Native(Native { node: Some(id), .. }) => Ok(*id),
_ => Err("this function cannot be customized with show")?,
}
}
@@ -138,8 +142,8 @@ struct Native {
pub func: fn(&mut Context, &mut Args) -> TypResult<Value>,
/// The set rule.
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
- /// The show rule.
- pub show: Option<fn(Func, Span) -> StyleMap>,
+ /// The id of the node to customize with this function's show rule.
+ pub node: Option<NodeId>,
}
impl Hash for Native {
@@ -147,7 +151,7 @@ impl Hash for Native {
self.name.hash(state);
(self.func as usize).hash(state);
self.set.map(|set| set as usize).hash(state);
- self.show.map(|show| show as usize).hash(state);
+ self.node.hash(state);
}
}
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index 017591b4..b3674dff 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -96,7 +96,7 @@ pub fn call_mut(
},
Value::Dict(dict) => match method {
- "remove" => dict.remove(args.expect("key")?).at(span)?,
+ "remove" => dict.remove(&args.expect("key")?).at(span)?,
_ => missing()?,
},
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 5542de89..e9d62a69 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -38,7 +38,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
use crate::library;
-use crate::model::{Content, StyleMap};
+use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap};
use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned};
use crate::util::EcoString;
@@ -79,8 +79,9 @@ fn eval_markup(
eval_markup(ctx, scp, nodes)?.styled_with_map(styles)
}
MarkupNode::Expr(Expr::Show(show)) => {
- let styles = show.eval(ctx, scp)?;
- eval_markup(ctx, scp, nodes)?.styled_with_map(styles)
+ let recipe = show.eval(ctx, scp)?;
+ eval_markup(ctx, scp, nodes)?
+ .styled_with_entry(StyleEntry::Recipe(recipe).into())
}
MarkupNode::Expr(Expr::Wrap(wrap)) => {
let tail = eval_markup(ctx, scp, nodes)?;
@@ -434,8 +435,17 @@ impl Eval for FieldAccess {
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let object = self.object().eval(ctx, scp)?;
+ let span = self.field().span();
+ let field = self.field().take();
+
Ok(match object {
- Value::Dict(dict) => dict.get(self.field().take()).at(self.span())?.clone(),
+ Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
+
+ Value::Content(Content::Show(_, Some(dict))) => dict
+ .get(&field)
+ .map_err(|_| format!("unknown field {field:?}"))
+ .at(span)?
+ .clone(),
v => bail!(
self.object().span(),
@@ -455,7 +465,7 @@ impl Eval for FuncCall {
Ok(match callee {
Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(),
- Value::Dict(dict) => dict.get(args.into_key()?).at(self.span())?.clone(),
+ Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
Value::Func(func) => {
let point = || Tracepoint::Call(func.name().map(ToString::to_string));
func.call(ctx, args).trace(point, self.span())?
@@ -615,13 +625,12 @@ impl Eval for SetExpr {
}
impl Eval for ShowExpr {
- type Output = StyleMap;
+ type Output = Recipe;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
// Evaluate the target function.
- let target = self.target();
- let target_span = target.span();
- let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?;
+ let pattern = self.pattern();
+ let pattern = pattern.eval(ctx, scp)?.cast::<Pattern>().at(pattern.span())?;
// Collect captured variables.
let captured = {
@@ -630,18 +639,24 @@ impl Eval for ShowExpr {
visitor.finish()
};
+ // Define parameters.
+ let mut params = vec![];
+ if let Some(binding) = self.binding() {
+ params.push((binding.take(), None));
+ }
+
// Define the recipe function.
let body = self.body();
- let body_span = body.span();
- let recipe = Func::from_closure(Closure {
+ let span = body.span();
+ let func = Func::from_closure(Closure {
name: None,
captured,
- params: vec![(self.binding().take(), None)],
+ params,
sink: None,
body,
});
- Ok(target.show(recipe, body_span).at(target_span)?)
+ Ok(Recipe { pattern, func, span })
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 352906aa..ba30348f 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -11,7 +11,7 @@ use crate::geom::{
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
};
use crate::library::text::RawNode;
-use crate::model::{Content, Layout, LayoutNode};
+use crate::model::{Content, Layout, LayoutNode, Pattern};
use crate::syntax::Spanned;
use crate::util::EcoString;
@@ -617,13 +617,13 @@ where
}
let sides = Sides {
- left: dict.get("left".into()).or_else(|_| dict.get("x".into())),
- top: dict.get("top".into()).or_else(|_| dict.get("y".into())),
- right: dict.get("right".into()).or_else(|_| dict.get("x".into())),
- bottom: dict.get("bottom".into()).or_else(|_| dict.get("y".into())),
+ left: dict.get(&"left".into()).or_else(|_| dict.get(&"x".into())),
+ top: dict.get(&"top".into()).or_else(|_| dict.get(&"y".into())),
+ right: dict.get(&"right".into()).or_else(|_| dict.get(&"x".into())),
+ bottom: dict.get(&"bottom".into()).or_else(|_| dict.get(&"y".into())),
}
.map(|side| {
- side.or_else(|_| dict.get("rest".into()))
+ side.or_else(|_| dict.get(&"rest".into()))
.and_then(|v| T::cast(v.clone()))
.unwrap_or_default()
});
@@ -684,6 +684,12 @@ castable! {
Value::Content(content) => content.pack(),
}
+castable! {
+ Pattern,
+ Expected: "function",
+ Value::Func(func) => Pattern::Node(func.node()?),
+}
+
#[cfg(test)]
mod tests {
use super::*;