summaryrefslogtreecommitdiff
path: root/src/eval/node.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval/node.rs')
-rw-r--r--src/eval/node.rs213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/eval/node.rs b/src/eval/node.rs
new file mode 100644
index 00000000..58b29483
--- /dev/null
+++ b/src/eval/node.rs
@@ -0,0 +1,213 @@
+use std::convert::TryFrom;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::mem;
+use std::ops::{Add, AddAssign};
+
+use crate::diag::StrResult;
+use crate::geom::SpecAxis;
+use crate::layout::{Layout, PackedNode};
+use crate::library::{
+ Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing,
+};
+use crate::util::EcoString;
+
+/// A partial representation of a layout node.
+///
+/// A node is a composable intermediate representation that can be converted
+/// into a proper layout node by lifting it to the block or page level.
+#[derive(Clone)]
+pub enum Node {
+ /// A word space.
+ Space,
+ /// A line break.
+ Linebreak,
+ /// A paragraph break.
+ Parbreak,
+ /// A page break.
+ Pagebreak,
+ /// Plain text.
+ Text(EcoString),
+ /// Spacing.
+ Spacing(SpecAxis, Spacing),
+ /// An inline node.
+ Inline(PackedNode),
+ /// A block node.
+ Block(PackedNode),
+ /// A sequence of nodes (which may themselves contain sequences).
+ Seq(Vec<Self>),
+}
+
+impl Node {
+ /// Create an empty node.
+ pub fn new() -> Self {
+ Self::Seq(vec![])
+ }
+
+ /// Create an inline-level node.
+ pub fn inline<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ Self::Inline(node.pack())
+ }
+
+ /// Create a block-level node.
+ pub fn block<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ Self::Block(node.pack())
+ }
+
+ /// Decoration this node.
+ pub fn decorate(self, _: Decoration) -> Self {
+ // TODO(set): Actually decorate.
+ self
+ }
+
+ /// Lift to a type-erased block-level node.
+ pub fn into_block(self) -> PackedNode {
+ if let Node::Block(packed) = self {
+ packed
+ } else {
+ let mut packer = NodePacker::new();
+ packer.walk(self);
+ packer.into_block()
+ }
+ }
+
+ /// Lift to a document node, the root of the layout tree.
+ pub fn into_document(self) -> DocumentNode {
+ let mut packer = NodePacker::new();
+ packer.walk(self);
+ packer.into_document()
+ }
+
+ /// Repeat this template `n` times.
+ pub fn repeat(&self, n: i64) -> StrResult<Self> {
+ let count = usize::try_from(n)
+ .map_err(|_| format!("cannot repeat this template {} times", n))?;
+
+ // TODO(set): Make more efficient.
+ Ok(Self::Seq(vec![self.clone(); count]))
+ }
+}
+
+impl Debug for Node {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("<node>")
+ }
+}
+
+impl Default for Node {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl PartialEq for Node {
+ fn eq(&self, _: &Self) -> bool {
+ // TODO(set): Figure out what to do here.
+ false
+ }
+}
+
+impl Add for Node {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ // TODO(set): Make more efficient.
+ Self::Seq(vec![self, rhs])
+ }
+}
+
+impl AddAssign for Node {
+ fn add_assign(&mut self, rhs: Self) {
+ *self = mem::take(self) + rhs;
+ }
+}
+
+/// Packs a `Node` into a flow or whole document.
+struct NodePacker {
+ document: Vec<PageNode>,
+ flow: Vec<FlowChild>,
+ par: Vec<ParChild>,
+}
+
+impl NodePacker {
+ fn new() -> Self {
+ Self {
+ document: vec![],
+ flow: vec![],
+ par: vec![],
+ }
+ }
+
+ fn into_block(mut self) -> PackedNode {
+ self.parbreak();
+ FlowNode(self.flow).pack()
+ }
+
+ fn into_document(mut self) -> DocumentNode {
+ self.parbreak();
+ self.pagebreak();
+ DocumentNode(self.document)
+ }
+
+ fn walk(&mut self, node: Node) {
+ match node {
+ Node::Space => {
+ self.push_inline(ParChild::Text(' '.into()));
+ }
+ Node::Linebreak => {
+ self.push_inline(ParChild::Text('\n'.into()));
+ }
+ Node::Parbreak => {
+ self.parbreak();
+ }
+ Node::Pagebreak => {
+ self.pagebreak();
+ }
+ Node::Text(text) => {
+ self.push_inline(ParChild::Text(text));
+ }
+ Node::Spacing(axis, amount) => match axis {
+ SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)),
+ SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)),
+ },
+ Node::Inline(inline) => {
+ self.push_inline(ParChild::Node(inline));
+ }
+ Node::Block(block) => {
+ self.push_block(FlowChild::Node(block));
+ }
+ Node::Seq(list) => {
+ for node in list {
+ self.walk(node);
+ }
+ }
+ }
+ }
+
+ fn parbreak(&mut self) {
+ let children = mem::take(&mut self.par);
+ if !children.is_empty() {
+ self.flow.push(FlowChild::Node(ParNode(children).pack()));
+ }
+ }
+
+ fn pagebreak(&mut self) {
+ let children = mem::take(&mut self.flow);
+ self.document.push(PageNode(FlowNode(children).pack()));
+ }
+
+ fn push_inline(&mut self, child: ParChild) {
+ self.par.push(child);
+ }
+
+ fn push_block(&mut self, child: FlowChild) {
+ self.parbreak();
+ self.flow.push(child);
+ }
+}