summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/mod.rs22
-rw-r--r--src/eval/node.rs7
-rw-r--r--src/eval/styles.rs124
3 files changed, 138 insertions, 15 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index e0143f6c..c1f0b024 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -6,6 +6,8 @@ mod array;
mod dict;
#[macro_use]
mod value;
+#[macro_use]
+mod styles;
mod capture;
mod function;
mod node;
@@ -18,6 +20,7 @@ pub use dict::*;
pub use function::*;
pub use node::*;
pub use scope::*;
+pub use styles::*;
pub use value::*;
use std::cell::RefMut;
@@ -31,13 +34,12 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative, Spec};
use crate::image::ImageStore;
-use crate::library::{GridNode, TrackSizing};
+use crate::library::{GridNode, TextNode, TrackSizing};
use crate::loading::Loader;
use crate::source::{SourceId, SourceStore};
-use crate::style::Style;
use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned};
-use crate::util::{BoolExt, EcoString, RefMutExt};
+use crate::util::{EcoString, RefMutExt};
use crate::Context;
/// Evaluate a parsed source file into a module.
@@ -70,8 +72,8 @@ pub struct EvalContext<'a> {
pub modules: HashMap<SourceId, Module>,
/// The active scopes.
pub scopes: Scopes<'a>,
- /// The active style.
- pub style: Style,
+ /// The active styles.
+ pub styles: Styles,
}
impl<'a> EvalContext<'a> {
@@ -84,7 +86,7 @@ impl<'a> EvalContext<'a> {
route: vec![source],
modules: HashMap::new(),
scopes: Scopes::new(Some(&ctx.std)),
- style: ctx.style.clone(),
+ styles: Styles::new(),
}
}
@@ -158,14 +160,10 @@ impl Eval for Markup {
type Output = Node;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- let snapshot = ctx.style.clone();
-
let mut result = Node::new();
for piece in self.nodes() {
result += piece.eval(ctx)?;
}
-
- ctx.style = snapshot;
Ok(result)
}
}
@@ -179,11 +177,11 @@ impl Eval for MarkupNode {
Self::Linebreak => Node::Linebreak,
Self::Parbreak => Node::Parbreak,
Self::Strong => {
- ctx.style.text_mut().strong.flip();
+ ctx.styles.set(TextNode::STRONG, !ctx.styles.get(TextNode::STRONG));
Node::new()
}
Self::Emph => {
- ctx.style.text_mut().emph.flip();
+ ctx.styles.set(TextNode::EMPH, !ctx.styles.get(TextNode::EMPH));
Node::new()
}
Self::Text(text) => Node::Text(text.clone()),
diff --git a/src/eval/node.rs b/src/eval/node.rs
index 58b29483..4d259e2d 100644
--- a/src/eval/node.rs
+++ b/src/eval/node.rs
@@ -9,6 +9,7 @@ use crate::geom::SpecAxis;
use crate::layout::{Layout, PackedNode};
use crate::library::{
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing,
+ TextNode,
};
use crate::util::EcoString;
@@ -158,10 +159,10 @@ impl NodePacker {
fn walk(&mut self, node: Node) {
match node {
Node::Space => {
- self.push_inline(ParChild::Text(' '.into()));
+ self.push_inline(ParChild::Text(TextNode(' '.into())));
}
Node::Linebreak => {
- self.push_inline(ParChild::Text('\n'.into()));
+ self.push_inline(ParChild::Text(TextNode('\n'.into())));
}
Node::Parbreak => {
self.parbreak();
@@ -170,7 +171,7 @@ impl NodePacker {
self.pagebreak();
}
Node::Text(text) => {
- self.push_inline(ParChild::Text(text));
+ self.push_inline(ParChild::Text(TextNode(text)));
}
Node::Spacing(axis, amount) => match axis {
SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)),
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
new file mode 100644
index 00000000..30822edb
--- /dev/null
+++ b/src/eval/styles.rs
@@ -0,0 +1,124 @@
+use std::any::{Any, TypeId};
+use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+use std::rc::Rc;
+
+// Possible optimizations:
+// - Ref-count map for cheaper cloning and smaller footprint
+// - Store map in `Option` to make empty maps non-allocating
+// - Store small properties inline
+
+/// A map of style properties.
+#[derive(Default, Clone)]
+pub struct Styles {
+ map: HashMap<TypeId, Rc<dyn Any>>,
+}
+
+impl Styles {
+ /// Create a new, empty style map.
+ pub fn new() -> Self {
+ Self { map: HashMap::new() }
+ }
+
+ /// Set the value for a style property.
+ pub fn set<P: Property>(&mut self, _: P, value: P::Value) {
+ self.map.insert(TypeId::of::<P>(), Rc::new(value));
+ }
+
+ /// Get the value of a copyable style property.
+ ///
+ /// Returns the property's default value if the map does not contain an
+ /// entry for it.
+ pub fn get<P: Property>(&self, key: P) -> P::Value
+ where
+ P::Value: Copy,
+ {
+ self.get_inner(key).copied().unwrap_or_else(P::default)
+ }
+
+ /// Get a reference to a style property.
+ ///
+ /// Returns a reference to the property's default value if the map does not
+ /// contain an entry for it.
+ pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
+ self.get_inner(key).unwrap_or_else(|| P::default_ref())
+ }
+
+ /// Get a reference to a style directly in this map.
+ fn get_inner<P: Property>(&self, _: P) -> Option<&P::Value> {
+ self.map
+ .get(&TypeId::of::<P>())
+ .and_then(|boxed| boxed.downcast_ref())
+ }
+}
+
+impl Debug for Styles {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ // TODO(set): Better debug printing possible?
+ f.pad("Styles(..)")
+ }
+}
+
+/// Stylistic property keys.
+pub trait Property: 'static {
+ /// The type of this property, for example, this could be
+ /// [`Length`](crate::geom::Length) for a `WIDTH` property.
+ type Value;
+
+ /// The default value of the property.
+ fn default() -> Self::Value;
+
+ /// A static reference to the default value of the property.
+ ///
+ /// This is automatically implemented through lazy-initialization in the
+ /// `properties!` macro. This way, expensive defaults don't need to be
+ /// recreated all the time.
+ fn default_ref() -> &'static Self::Value;
+}
+
+macro_rules! set {
+ ($ctx:expr, $target:expr => $source:expr) => {
+ if let Some(v) = $source {
+ $ctx.styles.set($target, v);
+ }
+ };
+}
+
+macro_rules! properties {
+ ($node:ty, $(
+ $(#[$attr:meta])*
+ $name:ident: $type:ty = $default:expr
+ ),* $(,)?) => {
+ // TODO(set): Fix possible name clash.
+ mod properties {
+ use std::marker::PhantomData;
+ use super::*;
+
+ $(#[allow(non_snake_case)] mod $name {
+ use $crate::eval::Property;
+ use once_cell::sync::Lazy;
+ use super::*;
+
+ pub struct Key<T>(pub PhantomData<T>);
+
+ impl Property for Key<$type> {
+ type Value = $type;
+
+ fn default() -> Self::Value {
+ $default
+ }
+
+ fn default_ref() -> &'static Self::Value {
+ static LAZY: Lazy<$type> = Lazy::new(|| $default);
+ &*LAZY
+ }
+ }
+ })*
+
+ impl $node {
+ $($(#[$attr])* pub const $name: $name::Key<$type>
+ = $name::Key(PhantomData);)*
+ }
+ }
+ };
+}