summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-26 14:29:10 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-26 14:29:10 +0200
commit3e052e6e017eaf51b101fa87629e392c16c32ac2 (patch)
treebafe142ad3501b22be46401bd8e972d89d16d45e
parenta9869c212f7c1bc77a52e301ad014641b014e834 (diff)
All methods for groups
-rw-r--r--src/eval/methods.rs11
-rw-r--r--src/library/layout/locate.rs2
-rw-r--r--src/model/locate.rs236
3 files changed, 172 insertions, 77 deletions
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index 6ccd98e6..e8296d23 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -77,7 +77,16 @@ pub fn call(
}
"entry" => {
if let Some(group) = dynamic.downcast::<Group>() {
- Value::Content(Content::Locate(group.entry(args.expect("recipe")?)))
+ Value::Content(Content::Locate(
+ group.entry(args.expect("recipe")?, args.named("value")?),
+ ))
+ } else {
+ missing()?
+ }
+ }
+ "all" => {
+ if let Some(group) = dynamic.downcast::<Group>() {
+ Value::Content(Content::Locate(group.all(args.expect("recipe")?)))
} else {
missing()?
}
diff --git a/src/library/layout/locate.rs b/src/library/layout/locate.rs
index e94a48ba..74480b91 100644
--- a/src/library/layout/locate.rs
+++ b/src/library/layout/locate.rs
@@ -3,7 +3,7 @@ use crate::model::{Group, LocateNode};
/// Format content with access to its location on the page.
pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
- let node = LocateNode::new(args.expect("recipe")?);
+ let node = LocateNode::single(args.expect("recipe")?);
Ok(Value::Content(Content::Locate(node)))
}
diff --git a/src/model/locate.rs b/src/model/locate.rs
index fd48e5ad..10de70e1 100644
--- a/src/model/locate.rs
+++ b/src/model/locate.rs
@@ -3,7 +3,7 @@ use std::sync::Arc;
use super::Content;
use crate::diag::TypResult;
-use crate::eval::{Args, Dict, Func, Value};
+use crate::eval::{Args, Array, Dict, Func, Value};
use crate::frame::{Element, Frame};
use crate::geom::{Point, Transform};
use crate::syntax::Spanned;
@@ -21,8 +21,13 @@ impl Group {
}
/// Add an entry to the group.
- pub fn entry(&self, recipe: Spanned<Func>) -> LocateNode {
- LocateNode { recipe, group: Some(self.clone()) }
+ pub fn entry(&self, recipe: Spanned<Func>, value: Option<Value>) -> LocateNode {
+ LocateNode::entry(self.clone(), recipe, value)
+ }
+
+ /// Do something with all entries of a group.
+ pub fn all(&self, recipe: Spanned<Func>) -> LocateNode {
+ LocateNode::all(self.clone(), recipe)
}
}
@@ -32,57 +37,114 @@ impl Debug for Group {
}
}
-/// A node that can realize itself with its own location.
+/// A node that can be realized with pinned document locations.
#[derive(Debug, Clone, PartialEq, Hash)]
-pub struct LocateNode {
- recipe: Spanned<Func>,
- group: Option<Group>,
-}
+pub struct LocateNode(Arc<Repr>);
impl LocateNode {
- /// Create a new locate node.
- pub fn new(recipe: Spanned<Func>) -> Self {
- Self { recipe, group: None }
+ /// Create a new locatable single node.
+ pub fn single(recipe: Spanned<Func>) -> Self {
+ Self(Arc::new(Repr::Single(SingleNode(recipe))))
+ }
+
+ /// Create a new locatable group entry node.
+ pub fn entry(group: Group, recipe: Spanned<Func>, value: Option<Value>) -> Self {
+ Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value })))
+ }
+
+ /// Create a new all node with access to a group's members.
+ pub fn all(group: Group, recipe: Spanned<Func>) -> Self {
+ Self(Arc::new(Repr::All(AllNode { group, recipe })))
}
/// Realize the node.
pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
+ match self.0.as_ref() {
+ Repr::Single(single) => single.realize(ctx),
+ Repr::Entry(entry) => entry.realize(ctx),
+ Repr::All(all) => all.realize(ctx),
+ }
+ }
+}
+
+/// The different kinds of locate nodes.
+#[derive(Debug, Clone, PartialEq, Hash)]
+enum Repr {
+ /// A single `locate(me => ...)`.
+ Single(SingleNode),
+ /// A locatable group entry.
+ Entry(EntryNode),
+ /// A recipe for all entries of a group.
+ All(AllNode),
+}
+
+/// A solo locatable node.
+#[derive(Debug, Clone, PartialEq, Hash)]
+struct SingleNode(Spanned<Func>);
+
+impl SingleNode {
+ fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
let idx = ctx.pins.cursor();
- let pin = ctx.pins.next(self.group.clone());
+ let pin = ctx.pins.next(None, None);
+ let dict = pin.encode(None);
+ let args = Args::new(self.0.span, [Value::Dict(dict)]);
+ Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display())
+ }
+}
- // Determine the index among the peers.
- let index = self.group.as_ref().map(|_| {
- ctx.pins
- .iter()
- .filter(|other| {
- other.group == self.group && other.loc.flow < pin.loc.flow
- })
- .count()
- });
-
- let dict = pin.encode(index);
- let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]);
+/// A group node which can interact with its peer's details.
+#[derive(Debug, Clone, PartialEq, Hash)]
+struct EntryNode {
+ /// Which group the node belongs to, if any.
+ group: Group,
+ /// The recipe to execute.
+ recipe: Spanned<Func>,
+ /// An arbitrary attached value.
+ value: Option<Value>,
+}
- // Collect all members if requested.
- if self.group.is_some() && self.recipe.v.argc() == Some(2) {
- let mut all: Vec<_> =
- ctx.pins.iter().filter(|other| other.group == self.group).collect();
+impl EntryNode {
+ fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
+ let idx = ctx.pins.cursor();
+ let pin = ctx.pins.next(Some(self.group.clone()), self.value.clone());
- all.sort_by_key(|pin| pin.loc.flow);
+ // Determine the index among the peers.
+ let index = ctx
+ .pins
+ .iter()
+ .filter(|other| other.is_in(&self.group) && other.loc.flow < pin.loc.flow)
+ .count();
- let array = all
- .iter()
- .enumerate()
- .map(|(index, member)| Value::Dict(member.encode(Some(index))))
- .collect();
+ let dict = pin.encode(Some(index));
+ let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]);
- args.push(self.recipe.span, Value::Array(array))
+ // Collect all members if requested.
+ if self.recipe.v.argc() == Some(2) {
+ let all = ctx.pins.encode_group(&self.group);
+ args.push(self.recipe.span, Value::Array(all))
}
Ok(Content::Pin(idx) + self.recipe.v.call_detached(ctx, args)?.display())
}
}
+/// A node with access to the group's members without being one itself.
+#[derive(Debug, Clone, PartialEq, Hash)]
+struct AllNode {
+ /// Which group.
+ group: Group,
+ /// The recipe to execute.
+ recipe: Spanned<Func>,
+}
+
+impl AllNode {
+ fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
+ let all = ctx.pins.encode_group(&self.group);
+ let args = Args::new(self.recipe.span, [Value::Array(all)]);
+ Ok(self.recipe.v.call_detached(ctx, args)?.display())
+ }
+}
+
/// Manages pins.
#[derive(Debug, Clone, Hash)]
pub struct PinBoard {
@@ -94,43 +156,6 @@ pub struct PinBoard {
frozen: usize,
}
-/// A document pin.
-#[derive(Debug, Default, Clone, PartialEq, Hash)]
-pub struct Pin {
- /// The physical location of the pin in the document.
- loc: Location,
- /// The group the pin belongs to, if any.
- group: Option<Group>,
-}
-
-impl Pin {
- /// Encode into a user-facing dictionary.
- fn encode(&self, index: Option<usize>) -> Dict {
- let mut dict = dict! {
- "page" => Value::Int(self.loc.page as i64),
- "x" => Value::Length(self.loc.pos.x.into()),
- "y" => Value::Length(self.loc.pos.y.into()),
- };
-
- if let Some(index) = index {
- dict.insert("index".into(), Value::Int(index as i64));
- }
-
- dict
- }
-}
-
-/// A physical location in a document.
-#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
-pub struct Location {
- /// The page, starting at 1.
- pub page: usize,
- /// The exact coordinates on the page (from the top left, as usual).
- pub pos: Point,
- /// The flow index.
- pub flow: usize,
-}
-
impl PinBoard {
/// Create an empty pin board.
pub fn new() -> Self {
@@ -158,15 +183,18 @@ impl PinBoard {
}
/// Access the next pin.
- pub fn next(&mut self, group: Option<Group>) -> Pin {
+ pub fn next(&mut self, group: Option<Group>, value: Option<Value>) -> Pin {
if self.frozen > 0 {
return Pin::default();
}
let cursor = self.cursor;
self.jump(self.cursor + 1);
- self.pins[cursor].group = group;
- self.pins[cursor].clone()
+
+ let pin = &mut self.pins[cursor];
+ pin.group = group;
+ pin.value = value;
+ pin.clone()
}
/// The current cursor.
@@ -210,6 +238,16 @@ impl PinBoard {
pub fn resolved(&self, prev: &Self) -> usize {
self.pins.iter().zip(&prev.pins).filter(|(a, b)| a == b).count()
}
+
+ /// Encode a group into a user-facing array.
+ pub fn encode_group(&self, group: &Group) -> Array {
+ let mut all: Vec<_> = self.iter().filter(|other| other.is_in(group)).collect();
+ all.sort_by_key(|pin| pin.loc.flow);
+ all.iter()
+ .enumerate()
+ .map(|(index, member)| Value::Dict(member.encode(Some(index))))
+ .collect()
+ }
}
/// Locate all pins in a frame.
@@ -241,3 +279,51 @@ fn locate_impl(
}
}
}
+
+/// A document pin.
+#[derive(Debug, Default, Clone, PartialEq, Hash)]
+pub struct Pin {
+ /// The physical location of the pin in the document.
+ loc: Location,
+ /// The group the pin belongs to, if any.
+ group: Option<Group>,
+ /// An arbitrary attached value.
+ value: Option<Value>,
+}
+
+impl Pin {
+ /// Whether the pin is part of the given group.
+ fn is_in(&self, group: &Group) -> bool {
+ self.group.as_ref() == Some(group)
+ }
+
+ /// Encode into a user-facing dictionary.
+ fn encode(&self, index: Option<usize>) -> Dict {
+ let mut dict = dict! {
+ "page" => Value::Int(self.loc.page as i64),
+ "x" => Value::Length(self.loc.pos.x.into()),
+ "y" => Value::Length(self.loc.pos.y.into()),
+ };
+
+ if let Some(value) = &self.value {
+ dict.insert("value".into(), value.clone());
+ }
+
+ if let Some(index) = index {
+ dict.insert("index".into(), Value::Int(index as i64));
+ }
+
+ dict
+ }
+}
+
+/// A physical location in a document.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
+pub struct Location {
+ /// The page, starting at 1.
+ pub page: usize,
+ /// The exact coordinates on the page (from the top left, as usual).
+ pub pos: Point,
+ /// The flow index.
+ pub flow: usize,
+}