diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-03 11:44:53 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-03 13:35:39 +0100 |
| commit | 37a7afddfaffd44cb9bc013c9506599267e08983 (patch) | |
| tree | 20e7d62d3c5418baff01a21d0406b91bf3096214 /src/model | |
| parent | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff) | |
Split crates
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/args.rs | 2 | ||||
| -rw-r--r-- | src/model/array.rs | 10 | ||||
| -rw-r--r-- | src/model/cast.rs | 142 | ||||
| -rw-r--r-- | src/model/content.rs | 10 | ||||
| -rw-r--r-- | src/model/dict.rs | 8 | ||||
| -rw-r--r-- | src/model/eval.rs | 183 | ||||
| -rw-r--r-- | src/model/func.rs | 4 | ||||
| -rw-r--r-- | src/model/items.rs | 123 | ||||
| -rw-r--r-- | src/model/methods.rs | 2 | ||||
| -rw-r--r-- | src/model/mod.rs | 8 | ||||
| -rw-r--r-- | src/model/ops.rs | 21 | ||||
| -rw-r--r-- | src/model/str.rs | 18 | ||||
| -rw-r--r-- | src/model/styles.rs | 118 | ||||
| -rw-r--r-- | src/model/value.rs | 8 | ||||
| -rw-r--r-- | src/model/vm.rs | 23 |
15 files changed, 452 insertions, 228 deletions
diff --git a/src/model/args.rs b/src/model/args.rs index f95fbf08..9fb30b9c 100644 --- a/src/model/args.rs +++ b/src/model/args.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use super::{Array, Cast, Dict, Str, Value}; -use crate::diag::{At, SourceResult}; +use crate::diag::{bail, At, SourceResult}; use crate::syntax::{Span, Spanned}; /// Evaluated arguments to a function. diff --git a/src/model/array.rs b/src/model/array.rs index 196f02ec..053248ec 100644 --- a/src/model/array.rs +++ b/src/model/array.rs @@ -9,8 +9,9 @@ use crate::syntax::Spanned; use crate::util::ArcExt; /// Create a new [`Array`] from values. -#[allow(unused_macros)] -macro_rules! array { +#[macro_export] +#[doc(hidden)] +macro_rules! __array { ($value:expr; $count:expr) => { $crate::model::Array::from_vec(vec![$value.into(); $count]) }; @@ -20,6 +21,9 @@ macro_rules! array { }; } +#[doc(inline)] +pub use crate::__array as array; + /// A reference counted array with value semantics. #[derive(Default, Clone, PartialEq, Hash)] pub struct Array(Arc<Vec<Value>>); @@ -97,7 +101,7 @@ impl Array { .ok_or_else(|| out_of_bounds(index, len))?; Arc::make_mut(&mut self.0).remove(i); - return Ok(()); + Ok(()) } /// Extract a contigous subregion of the array. diff --git a/src/model/cast.rs b/src/model/cast.rs index 7356ef70..cbb2952d 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -1,8 +1,13 @@ use std::num::NonZeroUsize; +use std::str::FromStr; use super::{Pattern, Regex, Value}; use crate::diag::{with_alternative, StrResult}; -use crate::geom::{Corners, Dir, Paint, Sides}; +use crate::font::{FontStretch, FontStyle, FontWeight}; +use crate::frame::{Destination, Lang, Location, Region}; +use crate::geom::{ + Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides, +}; use crate::syntax::Spanned; use crate::util::EcoString; @@ -16,7 +21,9 @@ pub trait Cast<V = Value>: Sized { } /// Implement traits for dynamic types. -macro_rules! dynamic { +#[macro_export] +#[doc(hidden)] +macro_rules! __dynamic { ($type:ty: $name:literal, $($tts:tt)*) => { impl $crate::model::Type for $type { const TYPE_NAME: &'static str = $name; @@ -37,8 +44,13 @@ macro_rules! dynamic { }; } +#[doc(inline)] +pub use crate::__dynamic as dynamic; + /// Make a type castable from a value. -macro_rules! castable { +#[macro_export] +#[doc(hidden)] +macro_rules! __castable { ($type:ty: $inner:ty) => { impl $crate::model::Cast<$crate::model::Value> for $type { fn is(value: &$crate::model::Value) -> bool { @@ -88,6 +100,9 @@ macro_rules! castable { }; } +#[doc(inline)] +pub use crate::__castable as castable; + impl Cast for Value { fn is(_: &Value) -> bool { true @@ -119,14 +134,6 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> { } } -dynamic! { - Dir: "direction", -} - -dynamic! { - Regex: "regular expression", -} - castable! { usize, Expected: "non-negative integer", @@ -170,6 +177,10 @@ castable! { Value::Str(string) => string.into(), } +dynamic! { + Regex: "regular expression", +} + castable! { Pattern, Expected: "function, string or regular expression", @@ -178,6 +189,115 @@ castable! { @regex: Regex => Self::Regex(regex.clone()), } +dynamic! { + Dir: "direction", +} + +dynamic! { + GenAlign: "alignment", +} + +dynamic! { + Axes<GenAlign>: "2d alignment", +} + +castable! { + Axes<Option<GenAlign>>, + Expected: "1d or 2d alignment", + @align: GenAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Axes<GenAlign> => aligns.map(Some), +} + +dynamic! { + PartialStroke: "stroke", + Value::Length(thickness) => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + Value::Color(color) => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} + +castable! { + Axes<Rel<Length>>, + Expected: "array of two relative lengths", + Value::Array(array) => { + let mut iter = array.into_iter(); + match (iter.next(), iter.next(), iter.next()) { + (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), + _ => Err("point array must contain exactly two entries")?, + } + }, +} + +castable! { + Destination, + Expected: "string or dictionary with `page`, `x`, and `y` keys", + Value::Str(string) => Self::Url(string.into()), + Value::Dict(dict) => { + let page = dict.get("page")?.clone().cast()?; + let x: Length = dict.get("x")?.clone().cast()?; + let y: Length = dict.get("y")?.clone().cast()?; + Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) }) + }, +} + +castable! { + FontStyle, + Expected: "string", + Value::Str(string) => match string.as_str() { + "normal" => Self::Normal, + "italic" => Self::Italic, + "oblique" => Self::Oblique, + _ => Err(r#"expected "normal", "italic" or "oblique""#)?, + }, +} + +castable! { + FontWeight, + Expected: "integer or string", + Value::Int(v) => Value::Int(v) + .cast::<usize>()? + .try_into() + .map_or(Self::BLACK, Self::from_number), + Value::Str(string) => match string.as_str() { + "thin" => Self::THIN, + "extralight" => Self::EXTRALIGHT, + "light" => Self::LIGHT, + "regular" => Self::REGULAR, + "medium" => Self::MEDIUM, + "semibold" => Self::SEMIBOLD, + "bold" => Self::BOLD, + "extrabold" => Self::EXTRABOLD, + "black" => Self::BLACK, + _ => Err("unknown font weight")?, + }, +} + +castable! { + FontStretch, + Expected: "ratio", + Value::Ratio(v) => Self::from_ratio(v.get() as f32), +} + +castable! { + Lang, + Expected: "string", + Value::Str(string) => Self::from_str(&string)?, +} + +castable! { + Region, + Expected: "string", + Value::Str(string) => Self::from_str(&string)?, +} + impl<T: Cast> Cast for Option<T> { fn is(value: &Value) -> bool { matches!(value, Value::None) || T::is(value) diff --git a/src/model/content.rs b/src/model/content.rs index 1cffa773..372f6ff6 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,8 +9,9 @@ use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm}; +use crate as typst; use crate::diag::{SourceResult, StrResult}; -use crate::util::ReadableTypeId; +use crate::util::{EcoString, ReadableTypeId}; /// Composable representation of styled content. /// @@ -26,6 +27,11 @@ impl Content { SequenceNode(vec![]).pack() } + /// Create content from a string of text. + pub fn text(text: impl Into<EcoString>) -> Self { + item!(text)(text.into()) + } + /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec<Self>) -> Self { match seq.as_slice() { @@ -146,7 +152,7 @@ impl Add for Content { let mut lhs = self; if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() { if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() { - lhs_mut.0.extend(rhs_mut.0.drain(..)); + lhs_mut.0.append(&mut rhs_mut.0); } else if let Some(rhs) = rhs.downcast::<SequenceNode>() { lhs_mut.0.extend(rhs.0.iter().cloned()); } else { diff --git a/src/model/dict.rs b/src/model/dict.rs index 3e4fd956..49e50aa0 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -10,8 +10,9 @@ use crate::syntax::Spanned; use crate::util::ArcExt; /// Create a new [`Dict`] from key-value pairs. -#[allow(unused_macros)] -macro_rules! dict { +#[macro_export] +#[doc(hidden)] +macro_rules! __dict { ($($key:expr => $value:expr),* $(,)?) => {{ #[allow(unused_mut)] let mut map = std::collections::BTreeMap::new(); @@ -20,6 +21,9 @@ macro_rules! dict { }}; } +#[doc(inline)] +pub use crate::__dict as dict; + /// A reference-counted dictionary with value semantics. #[derive(Default, Clone, PartialEq, Hash)] pub struct Dict(Arc<BTreeMap<Str, Value>>); diff --git a/src/model/eval.rs b/src/model/eval.rs index 02617ed6..8e287f14 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -1,22 +1,19 @@ //! Evaluation of markup into modules. use std::collections::BTreeMap; -use std::sync::Arc; use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, + Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, }; -use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; +use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; -use crate::library::math; -use crate::library::text::TextNode; use crate::syntax::ast::TypedNode; use crate::syntax::{ast, SourceId, Span, Spanned, Unit}; -use crate::util::EcoString; +use crate::util::{format_eco, EcoString}; use crate::World; /// Evaluate a source file and return the resulting module. @@ -39,7 +36,7 @@ pub fn eval( // Evaluate the module. let route = unsafe { Route::insert(route, id) }; let ast = world.source(id).ast()?; - let std = &world.config().std; + let std = &world.config().scope; let scopes = Scopes::new(Some(std)); let mut vm = Vm::new(world, route.track(), Some(id), scopes); let result = ast.eval(&mut vm); @@ -136,8 +133,7 @@ fn eval_markup( break; } - eval_markup(vm, nodes)? - .styled_with_entry(StyleEntry::Recipe(recipe).into()) + eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe)) } ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => { let tail = eval_markup(vm, nodes)?; @@ -165,10 +161,13 @@ impl Eval for ast::MarkupNode { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { match self { - Self::Space(v) => v.eval(vm), + Self::Space(v) => Ok(match v.newlines() { + 0 ..= 1 => (vm.items.space)(), + _ => (vm.items.parbreak)(), + }), Self::Linebreak(v) => v.eval(vm), Self::Text(v) => v.eval(vm), - Self::Escape(v) => v.eval(vm), + Self::Escape(v) => Ok((vm.items.text)(v.get().into())), Self::Shorthand(v) => v.eval(vm), Self::SmartQuote(v) => v.eval(vm), Self::Strong(v) => v.eval(vm), @@ -187,23 +186,11 @@ impl Eval for ast::MarkupNode { } } -impl Eval for ast::Space { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(if self.newlines() < 2 { - (vm.items().space)() - } else { - (vm.items().parbreak)() - }) - } -} - impl Eval for ast::Linebreak { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().linebreak)(false)) + Ok((vm.items.linebreak)(false)) } } @@ -211,15 +198,7 @@ impl Eval for ast::Text { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(vm.text(self.get().clone())) - } -} - -impl Eval for ast::Escape { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(vm.text(self.get())) + Ok((vm.items.text)(self.get().clone())) } } @@ -227,7 +206,7 @@ impl Eval for ast::Shorthand { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(vm.text(self.get())) + Ok((vm.items.text)(self.get().into())) } } @@ -235,7 +214,7 @@ impl Eval for ast::SmartQuote { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().smart_quote)(self.double())) + Ok((vm.items.smart_quote)(self.double())) } } @@ -243,7 +222,7 @@ impl Eval for ast::Strong { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().strong)(self.body().eval(vm)?)) + Ok((vm.items.strong)(self.body().eval(vm)?)) } } @@ -251,7 +230,7 @@ impl Eval for ast::Emph { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().emph)(self.body().eval(vm)?)) + Ok((vm.items.emph)(self.body().eval(vm)?)) } } @@ -262,7 +241,7 @@ impl Eval for ast::Raw { let text = self.text().clone(); let lang = self.lang().cloned(); let block = self.block(); - Ok((vm.items().raw)(text, lang, block)) + Ok((vm.items.raw)(text, lang, block)) } } @@ -270,7 +249,7 @@ impl Eval for ast::Link { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().link)(self.url().clone())) + Ok((vm.items.link)(self.url().clone())) } } @@ -286,7 +265,7 @@ impl Eval for ast::Ref { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().ref_)(self.get().clone())) + Ok((vm.items.ref_)(self.get().clone())) } } @@ -296,7 +275,7 @@ impl Eval for ast::Heading { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { let level = self.level(); let body = self.body().eval(vm)?; - Ok((vm.items().heading)(level, body)) + Ok((vm.items.heading)(level, body)) } } @@ -304,7 +283,7 @@ impl Eval for ast::ListItem { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items().list_item)(self.body().eval(vm)?)) + Ok((vm.items.list_item)(self.body().eval(vm)?)) } } @@ -314,7 +293,7 @@ impl Eval for ast::EnumItem { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { let number = self.number(); let body = self.body().eval(vm)?; - Ok((vm.items().enum_item)(number, body)) + Ok((vm.items.enum_item)(number, body)) } } @@ -324,7 +303,7 @@ impl Eval for ast::DescItem { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { let term = self.term().eval(vm)?; let body = self.body().eval(vm)?; - Ok((vm.items().desc_item)(term, body)) + Ok((vm.items.desc_item)(term, body)) } } @@ -332,82 +311,76 @@ impl Eval for ast::Math { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let nodes = self - .children() - .map(|node| node.eval(vm)) - .collect::<SourceResult<_>>()?; - Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack()) + Ok((vm.items.math)( + self.children() + .map(|node| node.eval(vm)) + .collect::<SourceResult<_>>()?, + self.display(), + )) } } impl Eval for ast::MathNode { - type Output = math::MathNode; + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { Ok(match self { - Self::Space(_) => math::MathNode::Space, - Self::Linebreak(_) => math::MathNode::Linebreak, - Self::Escape(c) => math::MathNode::Atom(c.get().into()), - Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()), - Self::Script(node) => node.eval(vm)?, - Self::Frac(node) => node.eval(vm)?, - Self::Align(node) => node.eval(vm)?, - Self::Group(node) => math::MathNode::Row( - Arc::new( - node.children() - .map(|node| node.eval(vm)) - .collect::<SourceResult<_>>()?, - ), - node.span(), - ), - Self::Expr(expr) => { - let content = expr.eval(vm)?.display(vm.world); - if let Some(node) = content.downcast::<TextNode>() { - math::MathNode::Atom(node.0.clone()) - } else { - bail!(expr.span(), "expected text") - } - } + Self::Space(_) => (vm.items.space)(), + Self::Linebreak(v) => v.eval(vm)?, + Self::Escape(v) => (vm.items.math_atom)(v.get().into()), + Self::Atom(v) => v.eval(vm)?, + Self::Script(v) => v.eval(vm)?, + Self::Frac(v) => v.eval(vm)?, + Self::Align(v) => v.eval(vm)?, + Self::Group(v) => v.eval(vm)?, + Self::Expr(v) => match v.eval(vm)? { + Value::None => Content::empty(), + Value::Int(v) => (vm.items.math_atom)(format_eco!("{}", v)), + Value::Float(v) => (vm.items.math_atom)(format_eco!("{}", v)), + Value::Str(v) => (vm.items.math_atom)(v.into()), + Value::Content(v) => v, + _ => bail!(v.span(), "unexpected garbage"), + }, }) } } +impl Eval for ast::Atom { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok((vm.items.math_atom)(self.get().clone())) + } +} + impl Eval for ast::Script { - type Output = math::MathNode; + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(math::MathNode::Script(Arc::new(math::ScriptNode { - base: self.base().eval(vm)?, - sub: self - .sub() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - sup: self - .sup() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - }))) + Ok((vm.items.math_script)( + self.base().eval(vm)?, + self.sub().map(|node| node.eval(vm)).transpose()?, + self.sup().map(|node| node.eval(vm)).transpose()?, + )) } } impl Eval for ast::Frac { - type Output = math::MathNode; + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(math::MathNode::Frac(Arc::new(math::FracNode { - num: self.num().eval(vm)?.unparen(), - denom: self.denom().eval(vm)?.unparen(), - }))) + Ok((vm.items.math_frac)( + self.num().eval(vm)?, + self.denom().eval(vm)?, + )) } } impl Eval for ast::Align { - type Output = math::MathNode; + type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(math::MathNode::Align(self.count())) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok((vm.items.math_align)(self.count())) } } @@ -515,7 +488,7 @@ fn eval_code( } ast::Expr::Show(show) => { let recipe = show.eval(vm)?; - let entry = StyleEntry::Recipe(recipe).into(); + let entry = StyleEntry::Recipe(recipe); if vm.flow.is_some() { break; } @@ -627,7 +600,7 @@ impl Eval for ast::Unary { ast::UnOp::Neg => ops::neg(value), ast::UnOp::Not => ops::not(value), }; - Ok(result.at(self.span())?) + result.at(self.span()) } } @@ -676,7 +649,7 @@ impl ast::Binary { } let rhs = self.rhs().eval(vm)?; - Ok(op(lhs, rhs).at(self.span())?) + op(lhs, rhs).at(self.span()) } /// Apply an assignment operation. @@ -708,8 +681,7 @@ impl Eval for ast::FieldAccess { .to::<dyn Show>() .and_then(|node| node.field(&field)) .ok_or_else(|| format!("unknown field {field:?}")) - .at(span)? - .clone(), + .at(span)?, v => bail!( self.target().span(), @@ -754,9 +726,8 @@ impl Eval for ast::MethodCall { Ok(if methods::is_mutating(&method) { let args = self.args().eval(vm)?; - let mut value = self.target().access(vm)?; - methods::call_mut(&mut value, &method, args, span) - .trace(vm.world, point, span)?; + let value = self.target().access(vm)?; + methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?; Value::None } else { let value = self.target().eval(vm)?; @@ -882,7 +853,7 @@ impl Eval for ast::SetRule { let target = self.target(); let target = target.eval(vm)?.cast::<Func>().at(target.span())?; let args = self.args().eval(vm)?; - Ok(target.set(args)?) + target.set(args) } } @@ -1085,14 +1056,14 @@ impl Eval for ast::ModuleInclude { let span = self.path().span(); let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; let module = import(vm, &path, span)?; - Ok(module.content.clone()) + Ok(module.content) } } /// Process an import of a module relative to the current location. fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { // Load the source file. - let full = vm.locate(&path).at(span)?; + let full = vm.locate(path).at(span)?; let id = vm.world.resolve(&full).at(span)?; // Prevent cyclic importing. diff --git a/src/model/func.rs b/src/model/func.rs index dff58233..5be1aae3 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use comemo::{Track, Tracked}; use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; +use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::ast::{self, Expr, TypedNode}; use crate::syntax::{SourceId, SyntaxNode}; use crate::util::EcoString; @@ -229,7 +229,7 @@ impl Closure { } /// A visitor that determines which variables to capture for a closure. -pub struct CapturesVisitor<'a> { +pub(super) struct CapturesVisitor<'a> { external: &'a Scopes<'a>, internal: Scopes<'a>, captures: Scope, diff --git a/src/model/items.rs b/src/model/items.rs new file mode 100644 index 00000000..164d9602 --- /dev/null +++ b/src/model/items.rs @@ -0,0 +1,123 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::num::NonZeroUsize; + +use comemo::Tracked; +use once_cell::sync::OnceCell; + +use super::{Content, StyleChain}; +use crate::diag::SourceResult; +use crate::frame::Frame; +use crate::geom::{Abs, Dir}; +use crate::util::{hash128, EcoString}; +use crate::World; + +/// Global storage for lang items. +#[doc(hidden)] +pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new(); + +/// Set the lang items. This is a hack :( +/// +/// Passing the lang items everywhere they are needed (especially the text node +/// related things) is very painful. By storing them globally, in theory, we +/// break incremental, but only when different sets of lang items are used in +/// the same program. For this reason, if this function is called multiple +/// times, the items must be the same. +pub fn set_lang_items(items: LangItems) { + if LANG_ITEMS.set(items).is_err() { + let first = hash128(LANG_ITEMS.get().unwrap()); + let second = hash128(&items); + assert_eq!(first, second, "set differing lang items"); + } +} + +/// Access a lang item. +macro_rules! item { + ($name:ident) => { + $crate::model::LANG_ITEMS.get().unwrap().$name + }; +} + +/// Definition of certain standard library items the language is aware of. +#[derive(Copy, Clone)] +pub struct LangItems { + /// The root layout function. + pub root: + fn(world: Tracked<dyn World>, document: &Content) -> SourceResult<Vec<Frame>>, + /// Access the em size. + pub em: fn(StyleChain) -> Abs, + /// Access the text direction. + pub dir: fn(StyleChain) -> Dir, + /// A space. + pub space: fn() -> Content, + /// A forced line break. + pub linebreak: fn(justify: bool) -> Content, + /// Plain text. + pub text: fn(text: EcoString) -> Content, + /// A smart quote: `'` or `"`. + pub smart_quote: fn(double: bool) -> Content, + /// A paragraph break. + pub parbreak: fn() -> Content, + /// Strong content: `*Strong*`. + pub strong: fn(body: Content) -> Content, + /// Emphasized content: `_Emphasized_`. + pub emph: fn(body: Content) -> Content, + /// A raw block with optional syntax highlighting: `` `...` ``. + pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content, + /// A hyperlink: `https://typst.org`. + pub link: fn(url: EcoString) -> Content, + /// A reference: `@target`. + pub ref_: fn(target: EcoString) -> Content, + /// A section heading: `= Introduction`. + pub heading: fn(level: NonZeroUsize, body: Content) -> Content, + /// An item in an unordered list: `- ...`. + pub list_item: fn(body: Content) -> Content, + /// An item in an enumeration (ordered list): `1. ...`. + pub enum_item: fn(number: Option<usize>, body: Content) -> Content, + /// An item in a description list: `/ Term: Details`. + pub desc_item: fn(term: Content, body: Content) -> Content, + /// A math formula: `$x$`, `$ x^2 $`. + pub math: fn(children: Vec<Content>, display: bool) -> Content, + /// A atom in a formula: `x`, `+`, `12`. + pub math_atom: fn(atom: EcoString) -> Content, + /// A base with an optional sub- and superscript in a formula: `a_1^2`. + pub math_script: + fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, + /// A fraction in a formula: `x/2` + pub math_frac: fn(num: Content, denom: Content) -> Content, + /// An alignment indicator in a formula: `&`, `&&`. + pub math_align: fn(count: usize) -> Content, +} + +impl Debug for LangItems { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("LangItems { .. }") + } +} + +impl Hash for LangItems { + fn hash<H: Hasher>(&self, state: &mut H) { + (self.root as usize).hash(state); + (self.em as usize).hash(state); + (self.dir as usize).hash(state); + self.space.hash(state); + self.linebreak.hash(state); + self.text.hash(state); + self.smart_quote.hash(state); + self.parbreak.hash(state); + self.strong.hash(state); + self.emph.hash(state); + self.raw.hash(state); + self.link.hash(state); + self.ref_.hash(state); + self.heading.hash(state); + self.list_item.hash(state); + self.enum_item.hash(state); + self.desc_item.hash(state); + self.math.hash(state); + self.math_atom.hash(state); + self.math_script.hash(state); + self.math_frac.hash(state); + self.math_align.hash(state); + } +} diff --git a/src/model/methods.rs b/src/model/methods.rs index 07cbb822..26d27dfa 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -97,7 +97,7 @@ pub fn call( }, Value::Func(func) => match method { - "with" => Value::Func(func.clone().with(args.take())), + "with" => Value::Func(func.with(args.take())), _ => return missing(), }, diff --git a/src/model/mod.rs b/src/model/mod.rs index b4f8f653..fdebce0a 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,6 +1,8 @@ //! Layout and computation model. #[macro_use] +mod items; +#[macro_use] mod cast; #[macro_use] mod array; @@ -16,12 +18,11 @@ mod args; mod content; mod eval; mod func; +mod methods; +mod ops; mod scope; mod vm; -pub mod methods; -pub mod ops; - pub use self::str::*; pub use args::*; pub use array::*; @@ -30,6 +31,7 @@ pub use content::*; pub use dict::*; pub use eval::*; pub use func::*; +pub use items::*; pub use scope::*; pub use styles::*; pub use value::*; diff --git a/src/model/ops.rs b/src/model/ops.rs index 7eb814c1..ee126b03 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -1,12 +1,9 @@ //! Operations on values. -use std::cmp::Ordering; - -use super::{Node, Regex, Smart, Value}; +use super::{Regex, Smart, Value}; use crate::diag::StrResult; -use crate::geom::{Axes, Axis, Length, Numeric, Rel}; -use crate::library::text::TextNode; -use crate::library::{RawAlign, RawStroke}; +use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel}; +use std::cmp::Ordering; use Value::*; /// Bail with a type mismatch error. @@ -22,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { (a, None) => a, (None, b) => b, (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), - (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), + (Str(a), Content(b)) => Content(super::Content::text(a) + b), + (Content(a), Str(b)) => Content(a + super::Content::text(b)), (Content(a), Content(b)) => Content(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -88,14 +85,14 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Str(a), Str(b)) => Str(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), - (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), + (Content(a), Str(b)) => Content(a + super::Content::text(b)), + (Str(a), Content(b)) => Content(super::Content::text(a) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { - Value::dynamic(RawStroke { + Value::dynamic(PartialStroke { paint: Smart::Custom(color.into()), thickness: Smart::Custom(thickness), }) @@ -104,7 +101,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Dyn(a), Dyn(b)) => { // 1D alignments can be summed into 2D alignments. if let (Some(&a), Some(&b)) = - (a.downcast::<RawAlign>(), b.downcast::<RawAlign>()) + (a.downcast::<GenAlign>(), b.downcast::<GenAlign>()) { if a.axis() != b.axis() { Value::dynamic(match a.axis() { diff --git a/src/model/str.rs b/src/model/str.rs index 843da9a8..4aa40c54 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -5,19 +5,23 @@ use std::ops::{Add, AddAssign, Deref}; use unicode_segmentation::UnicodeSegmentation; -use super::{Array, Dict, Value}; +use super::{castable, dict, Array, Dict, Value}; use crate::diag::StrResult; -use crate::library::RawAlign; +use crate::geom::GenAlign; use crate::util::EcoString; /// Create a new [`Str`] from a format string. -#[allow(unused_macros)] -macro_rules! format_str { +#[macro_export] +#[doc(hidden)] +macro_rules! __format_str { ($($tts:tt)*) => {{ $crate::model::Str::from(format_eco!($($tts)*)) }}; } +#[doc(inline)] +pub use crate::__format_str as format_str; + /// An immutable reference counted string. #[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Str(EcoString); @@ -463,9 +467,9 @@ pub enum StrSide { castable! { StrSide, Expected: "start or end", - @align: RawAlign => match align { - RawAlign::Start => Self::Start, - RawAlign::End => Self::End, + @align: GenAlign => match align { + GenAlign::Start => Self::Start, + GenAlign::End => Self::End, _ => Err("expected either `start` or `end`")?, }, } diff --git a/src/model/styles.rs b/src/model/styles.rs index c58a1beb..24566b09 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,12 +7,11 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; -use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value}; +use super::{capability, Args, Content, Func, NodeId, Regex, Smart, Value}; use crate::diag::SourceResult; -use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides}; -use crate::library::layout::PageNode; -use crate::library::structure::{DescNode, EnumNode, ListNode}; -use crate::library::text::{ParNode, TextNode}; +use crate::geom::{ + Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, +}; use crate::syntax::Spanned; use crate::util::ReadableTypeId; use crate::World; @@ -111,9 +110,9 @@ impl StyleMap { self } - /// The highest-level kind of of structure the map interrupts. - pub fn interruption(&self) -> Option<Interruption> { - self.0.iter().filter_map(|entry| entry.interruption()).max() + /// Whether this map contains styles for the given `node.` + pub fn interrupts<T: 'static>(&self) -> bool { + self.0.iter().any(|entry| entry.is_of(NodeId::of::<T>())) } } @@ -132,17 +131,6 @@ impl Debug for StyleMap { } } -/// Determines whether a style could interrupt some composable structure. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Interruption { - /// The style forces a list break. - List, - /// The style forces a paragraph break. - Par, - /// The style forces a page break. - Page, -} - /// An entry for a single style property, recipe or barrier. #[derive(Clone, PartialEq, Hash)] pub enum StyleEntry { @@ -193,12 +181,12 @@ impl StyleEntry { } } - /// The highest-level kind of structure the entry interrupts. - pub fn interruption(&self) -> Option<Interruption> { + /// Whether this entry contains styles for the given `node.` + pub fn is_of(&self, node: NodeId) -> bool { match self { - Self::Property(property) => property.interruption(), - Self::Recipe(recipe) => recipe.interruption(), - _ => None, + Self::Property(property) => property.is_of(node), + Self::Recipe(recipe) => recipe.is_of(node), + _ => false, } } } @@ -397,7 +385,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { type Item = &'a K::Value; fn next(&mut self) -> Option<Self::Item> { - while let Some(entry) = self.entries.next() { + for entry in &mut self.entries { match entry { StyleEntry::Property(property) => { if let Some(value) = property.downcast::<K>() { @@ -662,9 +650,9 @@ impl Property { self.key == KeyId::of::<K>() } - /// Whether this property belongs to the node `T`. - pub fn is_of<T: 'static>(&self) -> bool { - self.node == NodeId::of::<T>() + /// Whether this property belongs to the node with the given id. + pub fn is_of(&self, node: NodeId) -> bool { + self.node == node } /// Access the property's value if it is of the given key. @@ -690,22 +678,6 @@ impl Property { pub fn make_scoped(&mut self) { self.scoped = true; } - - /// What kind of structure the property interrupts. - pub fn interruption(&self) -> Option<Interruption> { - if self.is_of::<PageNode>() { - Some(Interruption::Page) - } else if self.is_of::<ParNode>() { - Some(Interruption::Par) - } else if self.is_of::<ListNode>() - || self.is_of::<EnumNode>() - || self.is_of::<DescNode>() - { - Some(Interruption::List) - } else { - None - } - } } impl Debug for Property { @@ -826,7 +798,7 @@ impl Resolve for Em { if self.is_zero() { Abs::zero() } else { - self.at(styles.get(TextNode::SIZE)) + self.at(item!(em)(styles)) } } } @@ -891,6 +863,30 @@ where } } +impl Resolve for GenAlign { + type Output = Align; + + fn resolve(self, styles: StyleChain) -> Self::Output { + let dir = item!(dir)(styles); + match self { + Self::Start => dir.start().into(), + Self::End => dir.end().into(), + Self::Specific(align) => align, + } + } +} + +impl Resolve for PartialStroke { + type Output = PartialStroke<Abs>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + PartialStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + /// A property that is folded to determine its final value. pub trait Fold { /// The type of the folded output. @@ -970,6 +966,17 @@ impl Fold for Corners<Option<Rel<Abs>>> { } } +impl Fold for PartialStroke<Abs> { + type Output = Self; + + fn fold(self, outer: Self::Output) -> Self::Output { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + } + } +} + /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] pub struct Recipe { @@ -1003,13 +1010,14 @@ impl Recipe { } (Target::Text(text), Pattern::Regex(regex)) => { + let make = world.config().items.text; let mut result = vec![]; let mut cursor = 0; for mat in regex.find_iter(text) { let start = mat.start(); if cursor < start { - result.push(TextNode(text[cursor .. start].into()).pack()); + result.push(make(text[cursor .. start].into())); } result.push(self.call(world, || Value::Str(mat.as_str().into()))?); @@ -1021,7 +1029,7 @@ impl Recipe { } if cursor < text.len() { - result.push(TextNode(text[cursor ..].into()).pack()); + result.push(make(text[cursor ..].into())); } Content::sequence(result) @@ -1047,18 +1055,12 @@ impl Recipe { Ok(self.func.v.call_detached(world, args)?.display(world)) } - /// What kind of structure the property interrupts. - pub fn interruption(&self) -> Option<Interruption> { - if let Pattern::Node(id) = self.pattern { - if id == NodeId::of::<ListNode>() - || id == NodeId::of::<EnumNode>() - || id == NodeId::of::<DescNode>() - { - return Some(Interruption::List); - } + /// Whether this recipe is for the given node. + pub fn is_of(&self, node: NodeId) -> bool { + match self.pattern { + Pattern::Node(id) => id == node, + _ => false, } - - None } } diff --git a/src/model/value.rs b/src/model/value.rs index d68f42a0..07719883 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,11 +7,10 @@ use std::sync::Arc; use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; -use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str}; +use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Str}; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; -use crate::library::text::TextNode; -use crate::util::EcoString; +use crate::util::{format_eco, EcoString}; use crate::World; /// A computational value. @@ -385,7 +384,7 @@ primitive! { Str: "string", Str } primitive! { Content: "content", Content, None => Content::empty(), - Str(text) => TextNode(text.into()).pack() + Str(text) => Content::text(text) } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } @@ -395,6 +394,7 @@ primitive! { Args: "arguments", Args } #[cfg(test)] mod tests { use super::*; + use crate::model::{array, dict}; #[track_caller] fn test(value: impl Into<Value>, exp: &str) { diff --git a/src/model/vm.rs b/src/model/vm.rs index 4de57d1c..db0bf77c 100644 --- a/src/model/vm.rs +++ b/src/model/vm.rs @@ -2,11 +2,11 @@ use std::path::PathBuf; use comemo::Tracked; -use super::{Content, Route, Scopes, Value}; -use crate::diag::{SourceError, StrResult}; +use super::{LangItems, Route, Scopes, Value}; +use crate::diag::{error, SourceError, StrResult}; use crate::syntax::{SourceId, Span}; -use crate::util::{EcoString, PathExt}; -use crate::{LangItems, World}; +use crate::util::PathExt; +use crate::World; /// A virtual machine. pub struct Vm<'a> { @@ -20,6 +20,8 @@ pub struct Vm<'a> { pub scopes: Scopes<'a>, /// A control flow event that is currently happening. pub flow: Option<Flow>, + /// The language items. + pub items: LangItems, } impl<'a> Vm<'a> { @@ -36,6 +38,7 @@ impl<'a> Vm<'a> { location, scopes, flow: None, + items: world.config().items, } } @@ -54,18 +57,6 @@ impl<'a> Vm<'a> { Err("cannot access file system from here".into()) } - - /// The language items. - pub fn items(&self) -> &LangItems { - &self.world.config().items - } - - /// Create text content. - /// - /// This is a shorthand for `(vm.items().text)(..)`. - pub fn text(&self, text: impl Into<EcoString>) -> Content { - (self.items().text)(text.into()) - } } /// A control flow event that occurred during evaluation. |
