diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-05-03 15:58:15 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-05-03 15:58:15 +0200 |
| commit | d59109e8fffa1d0b03234329e12f5d3e578804e8 (patch) | |
| tree | fe7453da6f2ae327993e5ca6436ddc6a448a2c41 /src/eval | |
| parent | f77f1f61bf05ae506689be3c40252c5807276280 (diff) | |
Support recursive show rules
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 25 | ||||
| -rw-r--r-- | src/eval/capture.rs | 6 | ||||
| -rw-r--r-- | src/eval/dict.rs | 12 | ||||
| -rw-r--r-- | src/eval/func.rs | 42 | ||||
| -rw-r--r-- | src/eval/methods.rs | 2 | ||||
| -rw-r--r-- | src/eval/mod.rs | 41 | ||||
| -rw-r--r-- | src/eval/value.rs | 18 |
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::*; |
